2012-06-28 63 views
12

Tôi có một lớp Derived được kế thừa trực tiếp từ hai lớp cơ sở, Base1Base2. Tôi muốn biết nếu nó an toàn, nói chung, để so sánh các con trỏ tới các lớp cơ sở để xác định xem họ đều giống nhau Derived đối tượng:So sánh con trỏ thừa kế

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? 

Các con trỏ có bảo đảm là hợp lệ, nhưng không nhất thiết chỉ đối tượng Derived. Tôi đoán rằng điều này có lẽ là tốt, nhưng tôi muốn biết nếu nó là ok từ một quan điểm C + kỹ thuật. Tôi thực sự không bao giờ thực hiện bất kỳ thao tác nào trên con trỏ, tôi chỉ muốn biết liệu chúng có trỏ đến cùng một đối tượng hay không.

EDIT: Có vẻ như an toàn nếu tôi có thể đảm bảo rằng các đối tượng p1p2 trỏ đến Derrived đối tượng. Tôi về cơ bản muốn biết nếu nó là an toàn nếu họ không - nếu một hoặc cả hai điểm đến một đối tượng cơ sở, sẽ so sánh nhất thiết phải thất bại? Một lần nữa, tôi có thể đảm bảo các con trỏ hợp lệ (tức là, p1 sẽ không bao giờ trỏ đến một đối tượng Base2 hoặc ngược lại)

+0

Tôi đã có ấn tượng rằng c + + không hỗ trợ đa kế thừa. bạn có nghĩa là "Base1-> Base2-> Derived" –

+9

C++ thực sự hỗ trợ đa thừa kế :) – cwap

+0

Đó là một trong những cách dễ nhất để tự bắn mình vào chân, nhưng có, C++ hỗ trợ MI – Lucretiel

Trả lời

6

Vâng, không, nó sẽ không hoạt động.

Tôi đích thân một fan hâm mộ lớn của học-by-ví dụ, vì vậy đây là một:

#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; 
} 

Một chạy qua máy tính của tôi outputted này: d: 0035F9FC, b1: 0035F9FC, b2: 0035FA00, d.numberDerived: 0035FA04

Soo .. Nếu chúng tôi xác định địa chỉ của d là 0, thì b1 là 0, b2 là +4 và số d là +8. Điều này là do một int trên máy tính của tôi dài 4 byte.

Về cơ bản, bạn phải nhìn vào cách bố trí như thế nào C++ nội bộ đại diện cho một lớp:

Address: Class: 
0   Base1 
4   Base2 
8   Derived 

.. Vì vậy, trong tổng số, instantiating một lớp Derived sẽ phân bổ không gian cho các lớp cơ sở của lớp được thừa kế, và cuối cùng nhường chỗ cho chính đối tượng xuất phát. Vì chúng ta có 3 số nguyên ở đây, sẽ là 12 byte.

Bây giờ, những gì bạn đang yêu cầu (trừ khi tôi hiểu nhầm điều gì đó) là bạn có thể so sánh địa chỉ của các con trỏ lớp cơ sở khác nhau để xem chúng có trỏ đến cùng một đối tượng không, và câu trả lời là không - Không trực tiếp ít nhất, như trong ví dụ của tôi, b1 sẽ trỏ đến 0035F9FC, trong khi b2 sẽ trỏ đến 0035FA00. Trong C++, việc bù trừ này được thực hiện tại thời gian biên dịch. Bạn có thể có thể làm một số phép thuật với RIIA và sizeof() và xác định số tiền của một b2 bù đắp nên có thể so sánh với b1, nhưng sau đó bạn chạy vào tất cả các loại rắc rối khác như ảo. Trong ngắn hạn, tôi sẽ không khuyên bạn nên cách tiếp cận này.

Cách tốt hơn nhiều là truyền sang Derived * như ialiashkevich cho biết, tuy nhiên, điều đó sẽ áp đặt một vấn đề nếu đối tượng của bạn không phải là bản sao của Derived *.

(Disclaimer;. Tôi đã không được sử dụng C++ trong 3-4 năm, vì vậy tôi có thể là một chút tắt trò chơi của tôi Hãy nhẹ nhàng :))

1

Câu trả lời ngắn gọn là không, đây không phải là ý tưởng hay.

CHÚ Ý: Giả sử bạn muốn tương đương tùy chỉnh cho tất cả các lớp học, nếu bạn muốn kiểm tra xem chúng có cùng một đối tượng hay không, tốt hơn là làm (Derived *).

Một giải pháp tốt hơn nhiều là quá tải nhà điều hành == cho Base1, Base2Derived.

Giả sử Base1 có 1 tham số param1 cho sự bình đẳng và Base2 có một tham số param2 cho bình đẳng:

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

Xin lỗi Lucretiel, tôi nghĩ bạn đang kiểm tra sự bình đẳng đối tượng, không phải là nhận dạng đối tượng. Tôi để lại câu trả lời của tôi như là một tham chiếu đến khách truy cập trong tương lai, nhưng bạn nên chấp nhận câu trả lời của ialiashkevich –

5

Đúc để Derived* trước khi so sánh là đúng cách để đi.

Có một chủ đề tương tự: C++ pointer multi-inheritance fun

+1

"con trỏ được đảm bảo là hợp lệ, nhưng không nhất thiết phải trỏ đến một đối tượng có nguồn gốc". Đây không phải là để nói đề xuất của bạn sẽ không hoạt động, nhưng bạn phải sử dụng 'dynamic_cast' và bạn phải xử lý trường hợp cả hai đều trả về NULL (có thể hoặc không có nghĩa là chúng trỏ trên cùng một đối tượng). – eran

+0

Ồ, chúng sẽ không bao giờ là rỗng - tôi nghĩ tôi đã đề cập đến điều đó. Chúng được bảo đảm chỉ vào một đối tượng hợp lệ thuộc loại của chúng. – Lucretiel

+0

@Lucretiel, nếu bạn gọi 'dynamic_cast (p)' và 'p' trỏ tới đối tượng hợp lệ của loại' Base1', lệnh gọi sẽ trả về 'NULL'. Trực tiếp so sánh một diễn viên với hai 'Base1' sẽ luôn luôn trả về true, vì' NULL == NULL'. Tuy nhiên, hai Base1 'có thể không phải là cùng một đối tượng. – eran

0

Nó dường như là không hợp lệ, dựa trên câu hỏi này SO: How is C++'s multiple inheritance implemented?

Về cơ bản, vì cách các đối tượng được đặt ra trong bộ nhớ, một dàn diễn viên đến hoặc Base1* hoặc Base2* dẫn đến đột biến của con trỏ mà tôi không thể tự ý đảo ngược khi chạy mà không cần dynamic_cast, mà tôi muốn tránh. Cảm ơn mọi người!

+0

Bạn có thể tránh 'dynamic_cast' chỉ khi bạn biết chắc chắn loại đối tượng được chọn. Xin vui lòng xem câu trả lời của tôi để biết thêm. – eran

0

Sử dụng dynamic_cast, và xem ra cho 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? 

Không ai trong số họ là một giải pháp tốt . Việc đầu tiên sẽ không biên dịch, vì bạn không thể so sánh con trỏ của các loại không liên quan. Bản thứ hai sẽ không biên dịch (trừ khi Base1Base2 có liên quan thông qua kế thừa) vì cùng một lý do: bạn không thể static_cast cho con trỏ của loại không liên quan.

Tùy chọn thứ ba là borderline. Tức là, nó không đúng, nhưng nó sẽ làm việc trong nhiều trường hợp (miễn là thừa kế không phải là ảo).

Cách đúng đắn của việc so sánh cho danh tính sẽ được sử dụng dynamic_cast với loại có nguồn gốc và kiểm tra cho null:

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

Vâng, hóa ra con đường ngắn nhất để đạt được những gì bạn đang tìm kiếm là:

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

Tự động truyền tới void* giảm hiệu quả con trỏ đã cho lớp dẫn xuất nhất, vì vậy bạn được đảm bảo nếu cả hai điểm trên cùng một đối tượng, xác nhận sẽ không thành công.

Thật vậy, there are practical uses for dynamic-casting to void pointer ...

Edit: để trả lời chỉnh sửa của câu hỏi, so sánh không an toàn. Xét đoạn mã sau:

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

Cách bố trí bộ nhớ trong hai cơ sở khác nhau là tương tự như của một Derived (trên thực hiện phổ biến - stack mọc ngược của heap).static_cast đầu tiên rời khỏi con trỏ như hiện tại, nhưng con trỏ thứ hai di chuyển con trỏ sizeof(Base1) trở lại, vì vậy bây giờ cả hai đều trỏ trên &b1 và xác nhận thành công - mặc dù các đối tượng khác nhau.

Bạn chỉ nên sử dụng static_cast nếu bạn biết chắc chắn dàn diễn viên chính xác. Đây không phải là trường hợp của bạn, vì vậy bạn phải sử dụng dynamic_cast, có thể như được đề xuất ở trên.

Các vấn đề liên quan