Có một quan niệm sai lầm: kế thừa-outide khái niệm về OOP tinh khiết, mà C++ không - là không có gì hơn một "thành phần với một thành viên không tên, với khả năng phân rã".
Sự vắng mặt của các hàm ảo (và hàm hủy không phải là đặc biệt, theo nghĩa này) làm cho đối tượng của bạn không đa hình, nhưng nếu những gì bạn đang làm chỉ là "tái sử dụng hành vi và phơi bày giao diện gốc" yêu cầu.
Cấu trúc không nhất thiết phải được gọi một cách rõ ràng, vì cuộc gọi của chúng luôn bị xích theo đặc điểm kỹ thuật.
#include <iostream>
unsing namespace std;
class A
{
public:
A() { cout << "A::A()" << endl; }
~A() { cout << "A::~A()" << endl; }
void hello() { cout << "A::hello()" << endl; }
};
class B: public A
{
public:
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
int main()
{
B b;
b.hello();
return 0;
}
chí đầu ra
A::A()
B::B()
B::hello()
B::~B()
A::~A()
thực hiện một nhúng vào B với
class B
{
public:
A a;
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
rằng sẽ ra giống hệt nhau.
"Không lấy được nếu hàm hủy không phải là ảo" không phải là hậu quả bắt buộc C++, nhưng chỉ được chấp nhận không được viết (không có gì trong thông số về nó: ngoài quy tắc UB gọi xóa trên cơ sở) phát sinh trước C++ 99, khi OOP bởi thừa kế động và các hàm ảo là mô hình lập trình C++ duy nhất được hỗ trợ.
Tất nhiên, nhiều lập trình viên trên thế giới làm xương của chúng với loại trường đó (giống như dạy iostreams như nguyên thủy, sau đó chuyển sang mảng và con trỏ, và bài học cuối cùng giáo viên nói "oh. .. tehre cũng là STL có vectơ, chuỗi và các tính năng nâng cao khác ") và ngày nay, ngay cả khi C++ bị đa nguyên, vẫn nhấn mạnh với quy tắc OOP thuần túy này.
Trong mẫu của tôi A :: ~ A() không phải là ảo chính xác như A :: hello. Nó có nghĩa là gì?
Đơn giản: vì lý do tương tự, gọi A::hello
sẽ không dẫn đến việc gọi B::hello
, gọi số A::~A()
(bằng cách xóa) sẽ không dẫn đến B::~B()
. Nếu bạn có thể chấp nhận -rong phong cách lập trình của bạn- xác nhận đầu tiên, không có lý do nào bạn không thể chấp nhận số thứ hai. Trong mẫu của tôi, không có A* p = new B
sẽ nhận được delete p
vì A :: ~ A không phải là ảo và Tôi biết ý nghĩa của nó là.
Chính xác cùng một lý do sẽ không thực hiện, sử dụng ví dụ thứ hai cho B, A* p = &((new B)->a);
với delete p;
, mặc dù trường hợp thứ hai này hoàn toàn kép với trường hợp đầu tiên, trông không thú vị với bất kỳ ai.
Vấn đề duy nhất là "bảo trì", theo nghĩa là - mã yopur được xem bởi một lập trình viên OOP- sẽ từ chối nó, không phải vì nó sai, mà bởi vì anh ta đã được yêu cầu làm như vậy.Trong thực tế, "không lấy được nếu destructor không phải là ảo" là bởi vì hầu hết các lập trình viên beleave rằng có quá nhiều lập trình viên không biết họ không thể gọi xóa trên một con trỏ đến một cơ sở. (Xin lỗi nếu điều này là không lịch sự, nhưng sau hơn 30 năm kinh nghiệm lập trình tôi không thể nhìn thấy bất kỳ lý do nào khác!)
Nhưng câu hỏi của bạn là khác nhau:
Gọi B :: ~ B() (bằng cách xóa hoặc theo phạm vi kết thúc) sẽ luôn dẫn đến A :: ~ A() kể từ A (cho dù được nhúng hoặc thừa hưởng) là trong mọi trường hợp một phần của B.
Tiếp theo ý kiến Luchian: Không xác định hành vi ám chỉ ở trên một trong ý kiến của ông có liên quan đến một xóa trên một con trỏ-to-an-object's-base không có destructor ảo.
Theo trường OOP, kết quả này trong quy tắc "không xuất phát nếu không có destructor ảo tồn tại". Điều tôi chỉ ra, ở đây, là lý do của trường đó phụ thuộc vào thực tế là mọi đối tượng định hướng OOP phải đa hình và mọi thứ đều đa hình phải được định vị bằng con trỏ tới cơ sở, cho phép thay thế đối tượng . Bằng cách đưa ra những khẳng định đó, trường học cố tình làm mất khoảng cách giữa giao lộ và không thể thay thế, do đó chương trình OOP thuần túy sẽ không trải nghiệm UB đó.
Vị trí của tôi, đơn giản, thừa nhận rằng C++ không chỉ là OOP, và không phải tất cả các đối tượng C++ phải được định hướng theo mặc định, và thừa nhận OOP không phải lúc nào cũng là nhu cầu cần thiết. nhất thiết phải phục vụ để thay thế OOP.
std :: bản đồ không phải là đa hình nên không thể thay thế được. MyMap là giống nhau: KHÔNG đa hình và KHÔNG thể thay thế.
Nó chỉ đơn giản là phải tái sử dụng std :: bản đồ và vạch trần cùng một giao diện bản đồ ::. Và thừa kế chỉ là cách để tránh một bản mẫu dài của các hàm viết lại mà chỉ cần gọi những cái được sử dụng lại.
MyMap sẽ không có dtor ảo như std :: bản đồ không có. Và điều này - với tôi- là đủ để nói với một lập trình viên C++ rằng đây không phải là các đối tượng đa hình và không được sử dụng ở nơi khác.
Tôi phải thừa nhận vị trí này hiện không được chia sẻ bởi hầu hết các chuyên gia C++. Nhưng tôi nghĩ (ý kiến cá nhân duy nhất của tôi) đây chỉ là vì lịch sử của họ, liên quan đến OOP như một giáo điều để phục vụ, không phải vì nhu cầu C++. Với tôi C++ không phải là một ngôn ngữ OOP thuần túy và không nhất thiết phải luôn tuân theo mô hình OOP, trong một ngữ cảnh mà OOP không được theo dõi hoặc yêu cầu.
+1 Luôn ưu tiên bố cục thay vì thừa kế. Vẫn muốn có một số cách để giảm tất cả các mã boilerplate cần thiết cho gói. – daramarak
@daramarak: do đó, tôi, nếu chỉ có một cái gì đó như 'using attribute.insert;' có thể hoạt động! Mặt khác, nó khá hiếm khi bạn thực sự cần tất cả các phương thức, và gói cho cơ hội để cung cấp tên có ý nghĩa và lấy các loại mức cao hơn :) –
@daramarak: * Vẫn muốn có một số cách để giảm tất cả mã boilerplate cần thiết để gói *: có, đó là: thừa kế. Nhưng các lập trình viên tự thuyết phục họ không nên sử dụng nó ... bởi vì họ luôn có xu hướng giải thích nó là "là một". Nhưng đó không phải là một yêu cầu, chỉ là một sự thuyết phục công khai. –