2016-12-13 18 views
16
#include <iostream> 

using namespace std; 

struct Base 
{ 
    virtual ~Base() 
    { 
     cout << "~Base(): " << b << endl; 
    } 

    int b = 1; 
}; 

struct Derived : Base 
{ 
    ~Derived() override 
    { 
     cout << "~Derived(): " << d << endl; 
    } 

    int d = 2; 
}; 

int main() 
{ 
    Base* p = new Derived[4]; 
    delete[] p; 
} 

Đầu ra là như follws: (Visual Studio 2015 với Clang 3,8)Tại sao đa hình không áp dụng trên các mảng trong C++?

~Base(): 1 
~Base(): 2 
~Base(): -2071674928 
~Base(): 1 

Tại sao đa hình không áp dụng trên mảng trong C++?

+8

Đa hình chỉ hoạt động thông qua con trỏ và các phần tử của mảng không phải là con trỏ, chúng là các loại giá trị. Bạn sẽ cần một mảng con trỏ và xóa chúng mỗi cá nhân. (hoặc một vector của con trỏ thông minh) – Galik

+2

tôi đồng ý với nhận xét @Galik, nhưng nó hoạt động tốt cho tôi, hành vi đa hình thích hợp. sh-4.2 $ g ++ -std = C++ 11 -o chính * .cpp ~ Có nguồn gốc(): 2 ~ Cơ sở(): 1 ~ Nguồn gốc(): 2 ~ Cơ sở(): 1 ~ Có nguồn gốc(): 2 ~ Base(): 1 ~ Derived(): 2 ~ Base(): 1 – instance

+0

Điều này sẽ không hoạt động như mảng sử dụng số học con trỏ để truy cập mảng els mà trong trường hợp của bạn là sizeof (Base) khi mặt khác một phần tử trong của bạn mảng là sizeof (có nguồn gốc) trong hậu quả khi truy cập mảng el Bạn sẽ kết thúc trong địa chỉ sai. – user3655463

Trả lời

14

Bạn nhận được hành vi không xác định bởi vì toán tử delete[] không biết loại đối tượng nào được lưu trữ trong mảng, do đó, nó tin tưởng loại tĩnh để quyết định dời số đối tượng riêng lẻ. Tiêu chuẩn nói như sau:

Trong phương án thứ hai (xóa mảng) nếu loại động của đối tượng bị xóa khác với loại tĩnh, hành vi không xác định.

loại tĩnh của mảng cần phải phù hợp với loại yếu tố mà bạn sử dụng cho việc phân bổ:

Derived* p = new Derived[4]; // Obviously, this works 

This Q&A đi vào chi tiết hơn về lý do tại sao tiêu chuẩn có yêu cầu này.

Theo như khắc phục hành vi này, bạn cần phải tạo một mảng con trỏ, thông thường hoặc thông minh (tốt hơn là thông minh để đơn giản hóa việc quản lý bộ nhớ).

+1

Nó thực sự là một "biểu thức mảng xóa" dựa trên loại tĩnh và loại động là như nhau. 'operator delete []' là một cái gì đó khác (mặc dù một cái gì đó khác được sử dụng bởi một biểu thức xóa). – Peter

+1

Hoặc một vector của con trỏ thông minh để đơn giản hóa nó nhiều hơn. – immibis

20

Given,

Base* p = Derived[4]; 

C++ 11 Tiêu chuẩn làm

delete [] p; 

là hành vi không xác định.

5.3.5 Xóa

...

2 ... Trong phương án thứ hai (xóa mảng) nếu loại năng động của các đối tượng được xóa khác với kiểu tĩnh của nó , hành vi không xác định.

Từ quan điểm bố cục bộ nhớ, điều này cũng có lý do tại sao delete [] p; sẽ dẫn đến hành vi không xác định.

Nếu sizeof(Derived)N, new Derived[4] cấp phát bộ nhớ đó sẽ là một cái gì đó như:

+--------+--------+--------+--------+ 
| N | N | N | N | 
+--------+--------+--------+--------+ 

Nói chung, sizeof(Base) < = sizeof(Derived). Trong trường hợp của bạn, sizeof(Base) < sizeof(Derived) kể từ Derived có biến thành viên bổ sung.

Khi bạn sử dụng:

Base* p = new Derived[4]; 

bạn có:

p 
| 
V 
+--------+--------+--------+--------+ 
| N | N | N | N | 
+--------+--------+--------+--------+ 

p+1 điểm đến một nơi nào đó ở giữa của đối tượng đầu tiên kể từ sizeof(Base) < sizeof(Derived).

 p+1 
     | 
     V 
+--------+--------+--------+--------+ 
| N | N | N | N | 
+--------+--------+--------+--------+ 

Khi trình hủy được gọi trên p+1, con trỏ không trỏ đến đầu của đối tượng. Do đó, chương trình thể hiện các triệu chứng của hành vi không xác định.


Một liên quan Vấn đề

Do sự khác biệt về kích thước của BaseDerived, bạn không thể duyệt qua các phần tử của mảng được cấp phát động sử dụng p.

for (int i = 0; i < 4; ++i) 
{ 
    // Do something with p[i] 
    // will not work since p+i does not necessary point to an object 
    // boundary. 
} 
+0

giải thích rõ ràng và rõ ràng. – instance

+1

Từ quan điểm thiết kế của xem tôi không thể thấy lý do tại sao xóa không thể sử dụng thông tin từ con trỏ mảng. Trong các mảng đa hình Fortran được hoàn thành tốt. –

+1

@VladimirF, tôi không có câu trả lời cho điều đó. –

2

Trong khi hai câu trả lời khác (herehere) đã tiếp cận vấn đề từ khía cạnh kỹ thuật và giải thích rất rõ lý do tại sao các trình biên dịch sẽ có một nhiệm vụ bất khả thi cố gắng để biên dịch mã mà bạn đã đặt cho nó, họ đã làm không giải thích vấn đề khái niệm với câu hỏi của bạn.

Đa hình có thể chỉ hoạt động khi chúng ta nói về mối quan hệ Lớp-phân lớp. Vì vậy, khi chúng tôi có tình hình như bạn đã mã hóa chúng ta có:

enter image description here

Chúng tôi đang nói "Tất cả các trường hợp của Derived cũng là trường hợp của Base". Lưu ý rằng điều này phải giữ hoặc chúng tôi thậm chí không thể bắt đầu nói về đa hình. Trong trường hợp này nó giữ và do đó chúng ta có thể sử dụng con trỏ đến Derived nơi mã mong đợi một con trỏ đến Base.

Nhưng sau đó bạn đang cố gắng làm điều gì đó khác nhau:

enter image description here

Và ở đây chúng tôi có một vấn đề. Trong khi trong lý thuyết tập, chúng ta có thể nói rằng một tập hợp một phân lớp cũng là một tập hợp của một siêu lớp, nó không đúng trong lập trình. Vấn đề là phần nào tăng lên bởi thực tế là sự khác biệt là "chỉ có hai ký tự". Nhưng một loạt các yếu tố là một cách hoàn toàn khác biệt, sau đó bất kỳ một trong những yếu tố đó.

Có lẽ nếu bạn viết lại mã sử dụng std::array mẫu mà sẽ trở nên rõ ràng hơn:

#include <iostream> 
#include <array> 

struct Base 
{ 
    virtual ~Base() 
    { 
     std::cout << "~Base()" << std::endl; 
    } 
}; 

struct Derived : Base 
{ 
    ~Derived() override 
    { 
     std::cout << "~Derived()" << std::endl; 
    } 
}; 

int main() 
{ 
    std::array<Base, 4>* p = new std::array<Derived, 4>; 
    delete[] p; 
} 

Mã này rõ ràng không thể biên dịch, các mẫu được không trở thành các lớp con của nhau tùy thuộc vào các thông số của họ. Theo cách này, giống như mong đợi một con trỏ đến một lớp để trở thành một con trỏ đến một lớp hoàn toàn không liên quan, điều này sẽ không hoạt động.

Hy vọng điều này sẽ giúp một số người muốn hiểu rõ hơn về những gì đang xảy ra, thay vì giải thích kỹ thuật.

4

Nó phải làm với các khái niệm về covariance and contravariance. Tôi sẽ đưa ra một ví dụ mà hy vọng sẽ làm rõ mọi thứ.

Chúng tôi sẽ bắt đầu với một hệ thống phân cấp đơn giản. Giả sử chúng ta đang xử lý việc tạo và phá hủy các đối tượng trong thế giới thực. Chúng tôi có các đối tượng 3D (chẳng hạn như khối gỗ) và các đối tượng 2D (chẳng hạn như các tờ giấy). Đối tượng 2D có thể được xử lý như đối tượng 3D, ngoại trừ chiều cao không đáng kể. Đây là hướng phân lớp thích hợp, vì ngược lại không đúng; Đối tượng 2D có thể được đặt phẳng trên đầu trang của mỗi khác, một cái gì đó là rất khó khăn tốt nhất để làm với các đối tượng 3D tùy ý.

Thứ gì đó tạo ra các đối tượng, như máy in, là covariant. Giả sử bạn của bạn muốn mượn một máy in 3D, lấy mười vật mà nó tạo ra và dán chúng vào một cái hộp. Bạn có thể cung cấp cho họ một máy in 2D; nó sẽ in mười trang, và bạn của bạn sẽ dán chúng vào một cái hộp. Tuy nhiên, nếu người bạn đó muốn lấy mười vật thể 2D và dán chúng vào một thư mục, bạn không thể cung cấp cho họ máy in 3D.

Thứ gì đó tiêu thụ đồ vật, như máy cắt tài liệu, là contravariant. Bạn của bạn có thư mục với các trang được in, nhưng nó không bán được, vì vậy anh ấy muốn băm nhỏ nó. Bạn có thể cung cấp cho họ một máy hủy tài liệu 3D, giống như những cái công nghiệp được sử dụng để băm nhỏ những thứ như xe hơi, và nó sẽ hoạt động tốt; cho ăn các trang đó không gây khó khăn gì cả. Mặt khác, nếu anh ta muốn cắt hộp đồ vật anh ta nhận được trước đó, bạn không thể cho anh ta máy cắt giấy 2D, vì các vật thể có thể không vừa với khe.

Mảng là bất biến; cả hai đều tiêu thụ các đối tượng (có nhiệm vụ) và tạo ra các đối tượng (với truy cập mảng). Như một ví dụ về loại mối quan hệ này, hãy lấy một máy fax thuộc loại nào đó. Nếu bạn của bạn muốn có một máy fax, họ cần loại chính xác mà họ đang yêu cầu; họ không thể có một siêu hoặc phân lớp, vì bạn không thể gắn các vật thể 3D vào một khe cho giấy 2D, và bạn không thể liên kết các đối tượng được tạo ra bởi một máy fax 3D vào một cuốn sách.

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