Địa chỉ lớp con bằng địa chỉ lớp cơ sở ảo?


8

Chúng ta đều biết rằng khi sử dụng thừa kế đơn giản, địa chỉ của lớp dẫn xuất giống như địa chỉ của lớp cơ sở. Nhiều kế thừa làm cho điều đó không đúng sự thật.

Liệu thừa kế ảo cũng làm cho điều đó không đúng sự thật? Nói cách khác, mã sau đây có đúng không:

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}

1
reinterpret_castvới các lớp luôn tanh (ngoại trừ từ lớp này sang lớp khác void*và trở lại cùng lớp).
hyde

3
"Chúng ta đều biết rằng khi sử dụng thừa kế đơn giản, địa chỉ của lớp dẫn xuất giống như địa chỉ của lớp cơ sở" là một tuyên bố khá mạnh mẽ. Bạn có chắc chắn các tiêu chuẩn đảm bảo điều này?
hyde

7
Đó là một cách giải thích thú vị về ngôn ngữ của con người mà không có câu nào bắt đầu bằng "tất cả chúng ta đều biết điều đó" là đúng.
molbdnilo

5
"C ++ sẽ là một ngôn ngữ không thực tế nếu chúng ta chỉ dựa vào những gì tiêu chuẩn đảm bảo" Tôi không phát triển C ++ trong nhiều thập kỷ như những người khác có thể có, nhưng tôi không bao giờ phải vi phạm tiêu chuẩn hoặc dựa vào hành vi không xác định / không xác định trong các ứng dụng của mình.
Timo

2
@ user1610015 Câu đầu tiên của bạn không phải lúc nào cũng đúng với một số trình biên dịch chính và nó không đúng như vậy theo tiêu chuẩn C ++ nên phải có một số đặc điểm kỹ thuật khác (về trình biên dịch cụ thể hoặc ABI) đảm bảo cho trường hợp cụ thể của bạn.
Öö Tiib

Câu trả lời:


5

Chúng ta đều biết rằng khi sử dụng thừa kế đơn giản, địa chỉ của lớp dẫn xuất giống như địa chỉ của lớp cơ sở.

Tôi nghĩ rằng tuyên bố là không đúng sự thật. Trong đoạn mã dưới đây, chúng ta có một thừa kế đơn giản (không ảo) (không nhiều), nhưng các địa chỉ khác nhau.

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

và đầu ra cho VS2015 là:

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

Liệu thừa kế ảo cũng làm cho điều đó không đúng sự thật?

Nếu bạn thay đổi sự kế thừa trong mã trên thành ảo, kết quả sẽ giống nhau. vì vậy, ngay cả trong trường hợp thừa kế ảo, địa chỉ của các đối tượng cơ sở và dẫn xuất có thể khác nhau.


Trên thực tế g ++ cũng xác nhận trường hợp của bạn bằng mã sửa đổi bit: coliru.stacked-crooking.com/a/ccea741b7126ee8a
Öö Tiib

@ ÖTiib void main()được chấp nhận ngay cả trong các trình biên dịch MSVS hiện đại. BTW, cảm ơn vì nhận xét. Tôi đã cập nhật mã.
Gupta

1
Không, void main()không được chấp nhận. Nó phải được int main()theo tiêu chuẩn. Và xin vui lòng loại bỏ nó dynamic_castkhỏi mã, nó không cần thiết ở đó, và nó gây ra sự nhầm lẫn.
geza

2

Kết quả của reinterpret_cast<B*>(a);chỉ được đảm bảo để trỏ đến Bđối tượng kèm theo anếu đối tượng conaBđối tượng kèm theo là con trỏ có thể chuyển đổi được , xem [expr.static.cast] / 3 của tiêu chuẩn C ++ 17.

Đối tượng lớp dẫn xuất là con trỏ có thể chuyển đổi được với đối tượng lớp cơ sở chỉ khi đối tượng dẫn xuất có bố cục chuẩn , không có các thành viên dữ liệu không tĩnh trực tiếp và đối tượng lớp cơ sở là lớp con đầu tiên của lớp cơ sở. [basic.compound] /4.3

Có một virtuallớp cơ sở không đủ điều kiện để một lớp được bố trí tiêu chuẩn . [lớp] /7.2 .

Do đó, vì Bcó lớp cơ sở ảo và thành viên dữ liệu không tĩnh, bsẽ không trỏ đến Bđối tượng kèm theo mà thay vào đó b, giá trị con trỏ sẽ không thay đổi so avới.

Truy cập ithành viên như thể nó đang chỉ vào Bđối tượng sau đó có hành vi không xác định.

Bất kỳ đảm bảo nào khác sẽ đến từ ABI cụ thể của bạn hoặc thông số kỹ thuật khác.


2

Nhiều kế thừa làm cho điều đó không đúng sự thật.

Điều đó không hoàn toàn chính xác. Xem xét ví dụ này:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

Khi tạo một thể hiện của D, BCđược khởi tạo mỗi dụ tương ứng của họ A. Tuy nhiên, sẽ không có vấn đề gì nếu trường hợp Dcó cùng địa chỉ của thể hiện của Bnó và thể hiện tương ứng của nó A. Mặc dù không bắt buộc, đây chính xác là những gì xảy ra khi biên dịch với clang 11gcc 10:

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

Liệu thừa kế ảo cũng làm cho điều đó không đúng sự thật

Hãy xem xét một phiên bản sửa đổi của ví dụ trên:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

Sử dụng công virtualcụ xác định chức năng thường được sử dụng để tránh các cuộc gọi chức năng mơ hồ. Do đó, khi sử dụng tính virtualkế thừa, cả hai Bvà các Cthể hiện phải tạo một Athể hiện chung . Khi bắt đầu D, chúng tôi nhận được các địa chỉ sau:

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

Mã sau đây có đúng không

Không có lý do ở đây để sử dụng reinterpret_cast, thậm chí nhiều hơn, nó dẫn đến hành vi không xác định. Sử dụng static_castthay thế:

A* pA = static_cast<A*>(pB);

Cả hai diễn viên hành xử khác nhau trong ví dụ này. Các reinterpret_castsẽ diễn giải lại pBnhư một con trỏ đến A, nhưng con trỏ pAcó thể trỏ đến một địa chỉ khác nhau, như trong ví dụ trên (C vs A). Con trỏ sẽ được cập nhật chính xác nếu bạn sử dụng static_cast.


-2

Lý do abkhác nhau trong trường hợp của bạn là bởi vì, vì Akhông có bất kỳ phương thức ảo nào, Anên không duy trì a vtable. Mặt khác, Bkhông duy trì a vtable.

Khi bạn upcast đến A, trình biên dịch đủ thông minh để bỏ qua vtableý nghĩa cho B. Và do đó sự khác biệt về địa chỉ. Bạn không nên reinterpret_castquay lại B, nó sẽ không hoạt động.

Để xác minh yêu cầu của tôi, hãy thử thêm một virtualphương thức, nói virtual void foo() {}vào class A. Bây giờ Acũng sẽ duy trì a vtable. Do đó, downcast ( reinterpret_cast) đến B sẽ trả lại cho bạn bản gốc b.


vtables không liên quan ở đây.
mfnx

@mfnx Câu hỏi của OP Subclass address equal to virtual base class address? có liên quan đến kế thừa ảo. Và kế thừa ảo có tất cả để làm với vtables.
theWiseBro

Ví dụ của @walnut OP thực hiện kế thừa ảo. Lý do mà việc tuyển chọn sẽ cho kết quả sai là vì không có vtable trong lớp A. Đúng là điều này không nên được thực hiện và là bất hợp pháp, nhưng tốt, hãy thực tế.
theWiseBro

@geza Vâng, xin lỗi vì nhận xét ngu ngốc của tôi. Tuy nhiên, tôi nghi ngờ rằng việc thêm các phương thức ảo Asẽ đảm bảo rằng các địa chỉ khớp và các diễn viên hoạt động, trên lý thuyết hoặc trong thực tế. Tôi không thể xóa downvote của mình mà không chỉnh sửa bài đăng, vì nó đã bị khóa.
quả óc chó

"Lý do a và b khác nhau ở ...": họ có thể chia sẻ cùng một địa chỉ. Có vtable hay không không có nghĩa là bạn sẽ không có cùng địa chỉ. Lưu ý sự khác biệt tôi có với clang và gcc so với @gupta với VS2015.
mfnx
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.