2012-06-28 99 views
12

我有一個類Derived直接繼承兩個基類,Base1Base2。我想知道,如果它是安全的,在一般情況下,比較指針的基類,以確定它們是否相同Derived對象:多繼承指針比較

Base1* p1; 
Base2* p2; 

/* 
* Stuff happens here. p1 and p2 now point to valid objects of either their 
* base type or Derived 
*/ 

//assert(p1 == p2); //This is illegal 
assert(p1 == static_cast<Base1*>(p2)); //Is this ok? 
assert(static_cast<Derived*>(p1) == static_cast<Derived*>(p2)); //How about this? 

指針保證是有效的,但不一定指向到一個Derived對象。我的猜測是,這可能是好的,但我想知道從技術C++的角度來看它是否可以。我其實從來沒有對指針做任何操作,我只是想知道他們是否指向同一個對象。

編輯:這似乎是安全的,如果我可以保證p1p2指向Derrived對象。我基本上想知道,如果它們不安全 - 如果一個或兩個都指向一個基礎對象,比較必然會失敗嗎?再次,我可以保證指針是有效的(即,p1永遠不會指向一個Base2對象,反之亦然)

+0

我的印象是,C++不支持多重繼承。你的意思是「Base1-> Base2-> Derived」 –

+9

C++確實支持多重繼承:) – cwap

+0

這是在腳下拍攝自己的最簡單方法之一,但是,是的,C++支持MI – Lucretiel

回答

6

嗯,不,它不會工作。

我個人的學習按示例的大風扇,所以這裏是一個:

#include <iostream> 

class Base1 
{ 
public: 
    Base1() 
    { 
     numberBase1 = 1; 
    } 

    int numberBase1; 
}; 

class Base2 
{ 
public: 
    Base2() 
    { 
     numberBase2 = 2; 
    } 

    int numberBase2; 
}; 

class Derived : public Base1, public Base2 
{ 
public: 
    Derived() 
    { 
     numberDerived = 3; 
    } 

    int numberDerived; 
}; 

int main() 
{ 
    Derived d; 
    Base1 *b1 = &d; 
    Base2 *b2 = &d; 

    std::cout << "d: " << &d << ", b1: " << b1 << ", b2: " << b2 << ", d.numberDerived: " << &(d.numberDerived) << std::endl; 

    return 0; 
} 

一個運行通過我的電腦上輸出這樣的: d:0035F9FC,B1:0035F9FC,B2: 0035FA00,d.numberDerived:0035FA04

Soo ..如果我們將d的地址定義爲0,那麼b1爲0,b2爲+4,d的個數爲+8。這是因爲我的機器上的int長度是4個字節。

基本上,你要看看以C如何++內部表示一類的佈局:

Address: Class: 
0   Base1 
4   Base2 
8   Derived 

。所以總體而言,實例化一個派生類將分配空間派生類的基類,最後爲派生對象本身騰出空間。由於我們有3個整數,這將是12個字節。

現在,你問什麼(除非我誤解了某些東西)是,如果你可以比較不同基類指針的地址,看看它們是否指向相同的對象,答案是否定的 - 不是直接至少,在我的例子,B1將指向0035F9FC,而B2將指向0035FA00。在C++中,這個偏移量都是在編譯時完成的。

你可以用RIIA和sizeof()來做一些魔術,並確定一個偏移量b2應該與b1相差多少,但是你會遇到類似虛擬的其他麻煩。總之,我不會推薦這種方法。

一個更好的辦法是強制轉換爲派生*像ialiashkevich說,然而,這將如果你的目標是不是導出的實例徵收問題*。

(聲明;我沒有使用C++在3 - 4年,所以我可能是有點過我的比賽要溫柔:))

1

簡短答案是否定的,這通常不是一個好主意。

注意:這是假設你想定製等效於所有類的,如果你要檢查它們是否是同一個對象,這是更好地做(Derived *)

一個更好的解決方案是將==運算符過載爲Base1,Base2Derived

假設Base1有平等1個參數param1Base2有另一個參數param2平等:

virtual bool Base1::operator==(object& other){ 
    return false; 
} 

virtual bool Base1::operator==(Base1& other) 
{ 
    return this.param1 == other.param1; 
} 

virtual bool Base2::operator==(object& other){ 
    return false; 
} 

virtual bool Base2::operator==(Base2& other) 
{ 
    return this.param2 == other.param2; 
} 

virtual bool Derived::operator==(object& other){ 
    return false; 
} 

virtual bool Derived::operator==(Derived& other){ 
    return this.param1 == other.param1 && this.param2 == other.param2; 
} 

virtual bool Derived::operator==(Base1& other){ 
    return this.param1 == other.param1; 
} 

virtual bool Derived::operator==(Base2& other){ 
    return this.param2 == other.param2; 
} 
+0

對不起,Lucretiel,我以爲你在檢查對象的平等,而不是對象的身份。我將我的答案留作未來訪問者的參考,但您應該接受ialiashkevich的回答 –

5

比較之前鑄造到Derived*是正確的道路要走。

有類似的話題:C++ pointer multi-inheritance fun

+1

「指針保證有效,但不一定指向派生對象」。這並不是說你的建議不會起作用,但是你必須使用'dynamic_cast',並且你必須處理它們都返回NULL的情況(這可能或不可能意味着它們指向同一個對象)。 – eran

+0

哦,他們永遠不會空 - 我以爲我提到過。他們保證指向他們類型的有效對象。 – Lucretiel

+0

@Lucretiel,如果你調用'dynamic_cast (p)'和'p'指向'Base1'類型的有效對象,那麼調用將返回'NULL'。因爲'NULL == NULL',所以直接比較這種類型轉換爲兩個'Base1'將始終返回true。但是這兩個'Base1's可能不是同一個對象。 – eran

0

這似乎是無效的,在此基礎上SO問題: How is C++'s multiple inheritance implemented?

基本上,因爲這樣的對象在內存佈局,強制轉換爲或者Base1*Base2*都會導致指針突變,我不能在沒有dynamic_cast的情況下在運行時任意顛倒,這是我想避免的。感謝大家!

+0

只有當您知道所指向的對象的類型時,才能避免使用'dynamic_cast'。請參閱我的答案瞭解更多。 – eran

0

使用dynamic_cast,並觀看了NULL。

#include <cassert> 

struct Base1 { virtual ~Base1() {} }; 
struct Base2 { virtual ~Base2() {} }; 
struct Derived : Base1, Base2 {}; 

bool IsEqual(Base1 *p1, Base2 *p2) { 
    Derived *d1 = dynamic_cast<Derived*>(p1); 
    Derived *d2 = dynamic_cast<Derived*>(p2); 

    if(!d1 || !d2) return false; 
    return d1 == d2; 
} 

int main() { 
    Derived d; 
    Base1 *p1 = &d; 
    Base2 *p2 = &d; 
    Base1 b1; 
    Base2 b2; 

    assert(IsEqual(p1, p2)); 
    assert(!IsEqual(p1, &b2)); 
    assert(!IsEqual(&b1, p2)); 
    assert(!IsEqual(&b1, &b2)); 
} 
0
assert(p1 == p2);      //This is illegal 
assert(p1 == static_cast<Base1*>(p2)); //Is this ok? 
assert(static_cast<Derived*>(p1) 
     == static_cast<Derived*>(p2)); //How about this? 

他們都不是一個好解決。第一個不會編譯,因爲你不能比較無關類型的指針。第二個將不編譯要麼出於同樣的原因(除非Base1Base2通過繼承與):你不能static_cast到不相關類型的指針。

第三個選項是交界。也就是說,這是不正確的,但在許多情況下工作(只要繼承不是虛擬的)。

身份比較正確的方法是使用dynamic_cast的派生類型和檢查空:

{ 
    Derived *tmp = dynamic_cast<Derived*>(p1); 
    assert(tmp && tmp == dynamic_cast<Derived*>(p2)); 
{ 
0

嗯,真可謂達到你要尋找最短路徑是:

assert(dynamic_cast<void*>(p1) == dynamic_cast<void*>(p2)); 

動態投射到void*有效地將給定的指針下變換爲其最派生的類,所以如果兩個指向相同的對象,斷言不會失敗。

事實上,there are practical uses for dynamic-casting to void pointer ...

編輯:回答這個問題的編輯,比較安全。考慮下面的代碼:

Base2 b2; 
Base1 b1; 
assert(static_cast<Derived*>(&b1) == static_cast<Derived*>(&b2)); // succeeds! 

兩個不同鹼基的存儲器佈局類似於一個Derived的(在一個共同的實施 - 堆棧增長堆的對面)。第一個static_cast保持原樣,但第二個將指針sizeof(Base1)移回,所以現在它們都指向&b1,並且斷言成功 - 即使對象不同。

只有當您確定演員表演正確時,您才應該使用static_cast。這不是你的情況,所以你必須使用dynamic_cast,可能如上所述。