Tại sao chúng ta cần một hàm hủy ảo thuần trong C ++?


154

Tôi hiểu sự cần thiết của một kẻ hủy diệt ảo. Nhưng tại sao chúng ta cần một kẻ hủy diệt ảo thuần túy? Trong một trong những bài viết của C ++, tác giả đã đề cập rằng chúng ta sử dụng hàm hủy ảo thuần khi chúng ta muốn tạo một lớp trừu tượng.

Nhưng chúng ta có thể tạo một lớp trừu tượng bằng cách làm cho bất kỳ hàm thành viên nào là ảo thuần túy.

Vì vậy, câu hỏi của tôi là

  1. Khi nào chúng ta thực sự tạo ra một kẻ hủy diệt thuần ảo? Bất cứ ai có thể đưa ra một ví dụ thời gian thực tốt?

  2. Khi chúng ta tạo các lớp trừu tượng, có phải là một cách thực hành tốt để làm cho hàm hủy cũng thuần ảo? Nếu có..vậy tại sao?



14
@ Daniel- Các liên kết được đề cập không trả lời câu hỏi của tôi. Nó trả lời tại sao một hàm hủy ảo thuần nên có một định nghĩa. Câu hỏi của tôi là tại sao chúng ta cần một kẻ hủy diệt ảo thuần túy.
Đánh dấu

Tôi đã cố gắng tìm hiểu lý do, nhưng bạn đã đặt câu hỏi ở đây.
nsivakr

Câu trả lời:


119
  1. Có lẽ lý do thực sự mà các công cụ phá hủy ảo thuần túy được cho phép là để cấm chúng có nghĩa là thêm một quy tắc khác vào ngôn ngữ và không cần quy tắc này vì không có tác động xấu nào có thể đến từ việc cho phép một công cụ hủy ảo thuần túy.

  2. Không, ảo cũ đơn giản là đủ.

Nếu bạn tạo một đối tượng với các cài đặt mặc định cho các phương thức ảo của nó và muốn làm cho nó trừu tượng mà không buộc bất kỳ ai ghi đè bất kỳ phương thức cụ thể nào , bạn có thể làm cho hàm hủy ảo thuần túy. Tôi không thấy nhiều điểm trong đó nhưng nó có thể.

Lưu ý rằng vì trình biên dịch sẽ tạo ra một hàm hủy ngầm định cho các lớp dẫn xuất, nếu tác giả của lớp không làm như vậy, bất kỳ lớp dẫn xuất nào sẽ không trừu tượng. Do đó, có hàm hủy ảo thuần trong lớp cơ sở sẽ không tạo ra bất kỳ sự khác biệt nào cho các lớp dẫn xuất. Nó sẽ chỉ làm cho lớp cơ sở trở nên trừu tượng (cảm ơn vì @kappa nhận xét của ).

Người ta cũng có thể giả định rằng mọi lớp phái sinh có thể sẽ cần phải có mã dọn dẹp cụ thể và sử dụng hàm hủy ảo thuần túy như một lời nhắc để viết một nhưng điều này dường như bị chiếm đoạt (và không bị ràng buộc).

Lưu ý: Các destructor là phương pháp duy nhất mà ngay cả khi nó tinh khiết ảo để có một thực hiện để nhanh chóng có nguồn gốc lớp (chức năng ảo có tinh khiết có thể triển khai).

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

13
"có các hàm ảo thuần có thể có các triển khai" Sau đó, nó không phải là ảo thuần.
GManNickG

2
Nếu bạn muốn tạo một lớp trừu tượng, sẽ không đơn giản hơn khi chỉ làm cho tất cả các hàm tạo được bảo vệ?
bdonlan

78
@GMan, bạn đã nhầm, là ảo thuần có nghĩa là các lớp dẫn xuất phải ghi đè phương thức này, đây là trực giao để có một triển khai. Kiểm tra mã của tôi và nhận xét foof::barnếu bạn muốn tự mình xem.
Motti

15
@GMan: Câu hỏi thường gặp về C ++ cho biết "Lưu ý rằng có thể cung cấp định nghĩa cho một hàm ảo thuần túy, nhưng điều này thường gây nhầm lẫn cho người mới và tốt nhất nên tránh cho đến sau này." parashift.com/c++-faq-lite/abcs.html#faq-22.4 Wikipedia (pháo đài chính xác) cũng nói tương tự. Tôi tin rằng tiêu chuẩn ISO / IEC sử dụng thuật ngữ tương tự (không may là bản sao của tôi hiện đang hoạt động) ... Tôi đồng ý rằng nó khó hiểu và tôi thường không sử dụng thuật ngữ này mà không làm rõ khi tôi cung cấp một định nghĩa, đặc biệt là lập trình viên xung quanh mới hơn ...
leander

9
@Motti: Điều thú vị ở đây và cung cấp nhiều nhầm lẫn hơn là hàm hủy ảo thuần túy KHÔNG cần phải được ghi đè một cách rõ ràng trong lớp dẫn xuất (và khởi tạo). Trong trường hợp như vậy, định nghĩa ngầm được sử dụng :)
kappa

33

Tất cả những gì bạn cần cho một lớp trừu tượng là ít nhất một hàm ảo thuần túy. Bất kỳ chức năng sẽ làm; nhưng khi nó xảy ra, hàm hủy là thứ mà bất kỳ lớp nào cũng sẽ có, vì vậy nó luôn ở đó như một ứng cử viên. Hơn nữa, làm cho hàm hủy thuần ảo (trái ngược với chỉ ảo) không có tác dụng phụ hành vi nào ngoài việc làm cho lớp trừu tượng. Do đó, rất nhiều hướng dẫn kiểu khuyên rằng nên sử dụng bộ định hướng ảo thuần túy để chỉ ra rằng một lớp là trừu tượng nếu không có lý do nào khác ngoài việc nó cung cấp một vị trí nhất quán mà ai đó đọc mã có thể nhìn để xem lớp đó có trừu tượng không.


1
nhưng vẫn còn tại sao để cung cấp việc thực hiện các công cụ hủy diệt thuần tuý. Điều gì có thể xảy ra với nó Tôi tạo ra một kẻ hủy diệt thuần ảo và không cung cấp việc thực hiện nó. Tôi giả sử chỉ các con trỏ lớp cơ sở được khai báo và do đó hàm hủy cho lớp trừu tượng không bao giờ được gọi.
Krishna Oza

4
@Surfing: bởi vì một hàm hủy của lớp dẫn xuất ngầm gọi hàm hủy của lớp cơ sở của nó, ngay cả khi hàm hủy đó là thuần ảo. Vì vậy, nếu không có thực hiện cho nó bahavior không xác định sẽ xảy ra.
a.peganz

19

Nếu bạn muốn tạo một lớp cơ sở trừu tượng:

  • không thể được khởi tạo (vâng, điều này là không cần thiết với thuật ngữ "trừu tượng"!)
  • nhưng cần hành vi hủy diệt ảo (bạn dự định mang theo con trỏ đến ABC thay vì con trỏ đến các loại dẫn xuất và xóa qua chúng)
  • nhưng không cần bất kỳ công văn ảo khác hành vi cho các phương pháp khác (có thể có không có phương pháp khác? xem xét một cách đơn giản bảo vệ "tài nguyên" container mà cần một nhà xây dựng / destructor / nhiệm vụ nhưng không có nhiều khác)

... Thật dễ dàng để làm cho lớp trừu tượng bằng cách làm cho hàm hủy ảo thuần cung cấp một định nghĩa (thân phương thức) cho nó.

Đối với ABC giả thuyết của chúng tôi:

Bạn đảm bảo rằng nó không thể được khởi tạo (ngay cả bên trong lớp, đây là lý do tại sao các hàm tạo riêng có thể không đủ), bạn có được hành vi ảo mà bạn muốn cho hàm hủy và bạn không phải tìm và gắn thẻ một phương thức khác không Không cần công văn ảo là "ảo".


8

Từ những câu trả lời tôi đã đọc cho câu hỏi của bạn, tôi không thể suy ra một lý do chính đáng để thực sự sử dụng một công cụ phá hủy ảo thuần túy. Ví dụ: lý do sau đây không thuyết phục được tôi:

Có lẽ lý do thực sự mà các công cụ phá hủy ảo thuần túy được cho phép là để cấm chúng có nghĩa là thêm một quy tắc khác vào ngôn ngữ và không cần quy tắc này vì không có tác động xấu nào có thể đến từ việc cho phép một công cụ hủy ảo thuần túy.

Theo tôi, các hàm hủy ảo thuần có thể hữu ích. Ví dụ: giả sử bạn có hai lớp myClassA và myClassB trong mã của mình và myClassB kế thừa từ myClassA. Vì những lý do được Scott Meyers đề cập trong cuốn sách "C ++ hiệu quả hơn", Mục 33 "Làm cho các lớp không lá trở nên trừu tượng", tốt hơn hết là thực sự tạo ra một lớp trừu tượng myAbTHERClass mà myClassA và myClassB kế thừa. Điều này cung cấp sự trừu tượng tốt hơn và ngăn ngừa một số vấn đề phát sinh với, ví dụ, các bản sao đối tượng.

Trong quá trình trừu tượng hóa (tạo lớp myAbTHERClass), có thể không có phương thức nào của myClassA hoặc myClassB là một ứng cử viên tốt để trở thành một phương thức ảo thuần túy (là điều kiện tiên quyết để my AbTHERClass trở nên trừu tượng). Trong trường hợp này, bạn định nghĩa ảo thuần hủy của lớp trừu tượng.

Sau đây là một ví dụ cụ thể từ một số mã tôi đã tự viết. Tôi có hai lớp, Numerics / ChemistryParams có chung các thuộc tính. Do đó, tôi cho phép họ kế thừa từ IParams lớp trừu tượng. Trong trường hợp này, tôi hoàn toàn không có phương pháp nào có thể hoàn toàn là ảo. Ví dụ, phương thức setParameter phải có cùng một thân cho mọi lớp con. Sự lựa chọn duy nhất mà tôi có là tạo ra kẻ hủy diệt thuần IParams.

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

1
Tôi thích cách sử dụng này, nhưng một cách khác để "thực thi" quyền thừa kế là bằng cách tuyên bố hàm tạo IParamsẽ được bảo vệ, như đã lưu ý trong một số nhận xét khác.
rwols

4

Nếu bạn muốn dừng khởi tạo lớp cơ sở mà không thực hiện bất kỳ thay đổi nào trong lớp dẫn xuất đã được triển khai và đã thử nghiệm của mình, bạn triển khai một hàm hủy ảo thuần trong lớp cơ sở.


3

Ở đây tôi muốn nói khi nào chúng ta cần hàm hủy ảo và khi nào chúng ta cần hàm hủy ảo thuần

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. Khi bạn muốn rằng không ai có thể tạo trực tiếp đối tượng của lớp Base, hãy sử dụng hàm hủy ảo thuần túy virtual ~Base() = 0. Thông thường, ít nhất một chức năng ảo thuần túy là bắt buộc, hãy thực hiện virtual ~Base() = 0, như chức năng này.

  2. Khi bạn không cần điều trên, chỉ có bạn cần sự hủy diệt an toàn của đối tượng lớp Derogen

    Cơ sở * pBase = new Derogen (); xóa pBase; Bộ hủy ảo thuần không bắt buộc, chỉ có bộ hủy ảo mới thực hiện công việc.


2

Bạn đang đi vào những giả thuyết với những câu trả lời này, vì vậy tôi sẽ cố gắng đưa ra một lời giải thích đơn giản hơn, thực tế hơn cho rõ ràng.

Các mối quan hệ cơ bản của thiết kế hướng đối tượng là hai: IS-A và HAS-A. Tôi đã không làm cho những lên. Đó là những gì họ được gọi.

IS-A chỉ ra rằng một đối tượng cụ thể xác định là thuộc lớp nằm trên nó trong hệ thống phân cấp lớp. Một đối tượng chuối là một đối tượng trái cây nếu nó là một lớp con của lớp trái cây. Điều này có nghĩa là bất cứ nơi nào một lớp trái cây có thể được sử dụng, một quả chuối có thể được sử dụng. Nó không phải là phản xạ, mặc dù. Bạn không thể thay thế một lớp cơ sở cho một lớp cụ thể nếu lớp cụ thể đó được yêu cầu.

Has-a chỉ ra rằng một đối tượng là một phần của lớp tổng hợp và có mối quan hệ sở hữu. Điều đó có nghĩa là trong C ++, nó là một đối tượng thành viên và do đó, onus thuộc lớp sở hữu để loại bỏ nó hoặc trao quyền sở hữu trước khi phá hủy chính nó.

Hai khái niệm này dễ nhận ra hơn trong các ngôn ngữ thừa kế đơn hơn là trong một mô hình đa thừa kế như c ++, nhưng các quy tắc về cơ bản là giống nhau. Sự phức tạp xuất hiện khi danh tính lớp không rõ ràng, chẳng hạn như chuyển một con trỏ lớp Banana vào một hàm lấy con trỏ lớp Fruit.

Các chức năng ảo trước hết là một thứ thời gian. Nó là một phần của đa hình ở chỗ nó được sử dụng để quyết định chức năng nào sẽ chạy tại thời điểm nó được gọi trong chương trình đang chạy.

Từ khóa ảo là một chỉ thị của trình biên dịch để liên kết các hàm theo một thứ tự nhất định nếu có sự mơ hồ về danh tính lớp. Các hàm ảo luôn nằm trong các lớp cha (theo như tôi biết) và chỉ ra cho trình biên dịch rằng việc ràng buộc các hàm thành viên với tên của chúng sẽ diễn ra với hàm lớp con trước và hàm lớp cha sau.

Một lớp Fruit có thể có màu hàm ảo () trả về "KHÔNG" theo mặc định. Hàm màu của lớp Banana () trả về "VÀNG" hoặc "BROWN".

Nhưng nếu hàm lấy con trỏ Fruit gọi color () trên lớp Banana được gửi tới nó - hàm color () nào được gọi? Hàm thường gọi Fruit :: color () cho một đối tượng Fruit.

Đó sẽ là 99% thời gian không phải là những gì đã được dự định. Nhưng nếu Fruit :: color () được khai báo là ảo thì Banana: color () sẽ được gọi cho đối tượng vì hàm color () chính xác sẽ được liên kết với con trỏ Fruit tại thời điểm cuộc gọi. Thời gian chạy sẽ kiểm tra đối tượng mà con trỏ trỏ tới vì nó được đánh dấu ảo trong định nghĩa lớp Fruit.

Điều này khác với việc ghi đè một hàm trong một lớp con. Trong trường hợp đó, con trỏ Fruit sẽ gọi Fruit :: color () nếu tất cả những gì nó biết là con trỏ IS-A của Fruit.

Vì vậy, bây giờ ý tưởng về một "chức năng ảo thuần túy" xuất hiện. Đó là một cụm từ khá đáng tiếc vì độ tinh khiết không có gì để làm với nó. Điều đó có nghĩa là phương thức lớp cơ sở không bao giờ được gọi. Thật vậy, một hàm ảo thuần túy không thể được gọi. Nó vẫn phải được xác định, tuy nhiên. Một chữ ký chức năng phải tồn tại. Nhiều lập trình viên thực hiện một triển khai trống {} để hoàn thiện, nhưng trình biên dịch sẽ tạo ra một nội bộ nếu không. Trong trường hợp đó khi hàm được gọi ngay cả khi con trỏ là Fruit, Banana :: color () sẽ được gọi vì đây là cách thực hiện duy nhất của color ().

Bây giờ là mảnh ghép cuối cùng của câu đố: nhà xây dựng và kẻ hủy diệt.

Các nhà xây dựng ảo thuần túy là bất hợp pháp, hoàn toàn. Đó chỉ là ra ngoài.

Nhưng các hàm hủy ảo thuần hoạt động trong trường hợp bạn muốn cấm tạo một thể hiện của lớp cơ sở. Chỉ các lớp con có thể được khởi tạo nếu hàm hủy của lớp cơ sở là thuần ảo. quy ước là gán nó cho 0.

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

Bạn phải tạo ra một thực hiện trong trường hợp này. Trình biên dịch biết đây là những gì bạn đang làm và đảm bảo rằng bạn làm đúng, hoặc nó phàn nàn rằng nó không thể liên kết với tất cả các chức năng mà nó cần để biên dịch. Các lỗi có thể gây nhầm lẫn nếu bạn không đi đúng hướng về cách bạn đang mô hình hóa hệ thống phân cấp lớp của mình.

Vì vậy, trong trường hợp này, bạn bị cấm tạo các phiên bản của Fruit, nhưng được phép tạo các phiên bản của Banana.

Một lệnh gọi để xóa con trỏ Fruit trỏ đến một thể hiện của Banana sẽ gọi Banana :: ~ Banana () trước và sau đó gọi Fuit :: ~ Fruit (), luôn luôn. Bởi vì không có vấn đề gì, khi bạn gọi một hàm hủy lớp con, hàm hủy của lớp cơ sở phải tuân theo.

Có phải là một mô hình xấu? Nó phức tạp hơn trong giai đoạn thiết kế, vâng, nhưng nó có thể đảm bảo rằng việc liên kết chính xác được thực hiện trong thời gian chạy và chức năng của lớp con được thực hiện khi có sự mơ hồ về chính xác lớp con nào đang được truy cập.

Nếu bạn viết C ++ để bạn chỉ chuyển xung quanh các con trỏ lớp chính xác mà không có con trỏ chung chung hoặc mơ hồ, thì các hàm ảo không thực sự cần thiết. Nhưng nếu bạn yêu cầu các loại linh hoạt trong thời gian chạy (như trong Apple Banana Orange ==> Fruit), các chức năng trở nên dễ dàng và linh hoạt hơn với mã dự phòng ít hơn. Bạn không còn phải viết một hàm cho từng loại trái cây và bạn biết rằng mỗi loại trái cây sẽ phản ứng với màu () với chức năng chính xác của nó.

Tôi hy vọng lời giải thích dài dòng này củng cố khái niệm này hơn là gây nhầm lẫn mọi thứ. Có rất nhiều ví dụ hay để xem xét, xem xét đủ và thực sự chạy chúng và gây rối với chúng và bạn sẽ nhận được nó.


1

Đây là một chủ đề đã có từ một thập kỷ :) Đọc 5 đoạn cuối của Mục số 7 về cuốn sách "Hiệu quả C ++" để biết chi tiết, bắt đầu từ "Thỉnh thoảng có thể thuận tiện để cung cấp cho lớp một kẻ hủy diệt ảo thuần túy ...."


0

Bạn đã hỏi một ví dụ và tôi tin rằng những điều sau đây cung cấp lý do cho một hàm hủy ảo thuần túy. Tôi mong muốn được trả lời về việc liệu đây là một tốt lý do ...

Tôi không muốn bất cứ ai có thể ném error_baseloại, nhưng loại ngoại lệ error_oh_shuckserror_oh_blastcó chức năng giống hệt nhau và tôi không muốn viết nó hai lần. Sự phức tạp pImpl là cần thiết để tránh tiếp xúc std::stringvới khách hàng của tôi và việc sử dụngstd::auto_ptr bắt buộc phải xây dựng bản sao.

Tiêu đề công khai chứa các đặc tả ngoại lệ sẽ có sẵn cho máy khách để phân biệt các loại ngoại lệ khác nhau được thư viện của tôi ném ra:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

Và đây là cách thực hiện được chia sẻ:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

Lớp ex_ String, được giữ riêng tư, ẩn std :: string khỏi giao diện chung của tôi:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

Mã của tôi sau đó đưa ra một lỗi là:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

Việc sử dụng một mẫu cho errorlà một chút vô cớ. Nó tiết kiệm một chút mã với chi phí yêu cầu khách hàng bắt lỗi như:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

0

Có lẽ có một TRƯỜNG HỢP SỬ DỤNG THỰC SỰ khác của kẻ hủy diệt ảo thuần túy mà tôi thực sự không thể thấy trong các câu trả lời khác :)

Lúc đầu, tôi hoàn toàn đồng ý với câu trả lời được đánh dấu: Đó là bởi vì việc cấm phá hủy ảo thuần túy sẽ cần một quy tắc bổ sung trong đặc tả ngôn ngữ. Nhưng đó vẫn không phải là trường hợp sử dụng mà Mark đang kêu gọi :)

Đầu tiên hãy tưởng tượng điều này:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

và một cái gì đó như:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

Đơn giản - chúng tôi có giao diện Printablevà một số "container" giữ bất cứ thứ gì với giao diện này. Tôi nghĩ ở đây khá rõ ràng tại saoprint() phương thức là thuần ảo. Nó có thể có một số phần thân nhưng trong trường hợp không có triển khai mặc định, ảo thuần là một "triển khai" lý tưởng (= "phải được cung cấp bởi một lớp con cháu").

Và bây giờ hãy tưởng tượng chính xác như vậy ngoại trừ nó không phải để in mà là để phá hủy:

class Destroyable {
  virtual ~Destroyable() = 0;
};

Và cũng có thể có một container tương tự:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

Đó là trường hợp sử dụng đơn giản hóa từ ứng dụng thực tế của tôi. Sự khác biệt duy nhất ở đây là phương pháp "đặc biệt" (hàm hủy) được sử dụng thay vì "bình thường" print(). Nhưng lý do tại sao nó là thuần ảo vẫn giống nhau - không có mã mặc định cho phương thức. Một chút bối rối có thể là thực tế rằng PHẢI có một số hàm hủy hiệu quả và trình biên dịch thực sự tạo ra một mã trống cho nó. Nhưng từ quan điểm của một lập trình viên thuần ảo vẫn có nghĩa là: "Tôi không có bất kỳ mã mặc định nào, nó phải được cung cấp bởi các lớp dẫn xuất."

Tôi nghĩ rằng không có bất kỳ ý tưởng lớn nào ở đây, chỉ cần giải thích thêm rằng ảo thực sự hoạt động thực sự thống nhất - cũng cho các kẻ hủy diệt.


-2

1) Khi bạn muốn yêu cầu các lớp dẫn xuất làm sạch. Điều này là hiếm.

2) Không, nhưng bạn muốn nó là ảo, mặc dù.


-2

Chúng ta cần tạo ra các phần tử ảo của hàm hủy vì thực tế là, nếu chúng ta không tạo phần tử ảo thì trình biên dịch sẽ chỉ hủy nội dung của lớp cơ sở, n tất cả các lớp dẫn xuất sẽ không thay đổi, trình biên dịch bacuse sẽ không gọi hàm hủy của bất kỳ phần tử nào khác lớp trừ lớp cơ sở.


-1: Câu hỏi không phải là tại sao một hàm hủy phải là ảo.
Troubadour

Hơn nữa, trong một số trường hợp, các công cụ phá hủy không phải là ảo để đạt được sự hủy diệt chính xác. Các hàm hủy ảo chỉ cần thiết khi bạn kết thúc việc gọi deletemột con trỏ tới lớp cơ sở trong khi thực tế nó trỏ đến đạo hàm của nó.
CygnusX1

Bạn đúng 100%. Đây là và đã từng là một trong những nguồn rò rỉ và sự cố số một trong các chương trình C ++, chỉ đứng thứ ba để cố gắng thực hiện mọi thứ với con trỏ null và vượt quá giới hạn của mảng. Một hàm hủy của lớp cơ sở không ảo sẽ được gọi trên một con trỏ chung, bỏ qua toàn bộ hàm hủy của lớp con nếu nó không được đánh dấu ảo. Nếu có bất kỳ đối tượng được tạo động nào thuộc về lớp con, chúng sẽ không được phục hồi bởi hàm hủy cơ sở trong lệnh gọi để xóa. Bạn đang theo đuổi tốt sau đó BLUURRK! (cũng khó tìm ở đâu.)
Chris Reid
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.