shared_ptr magic :)


89

Ông Lidström và tôi đã có một cuộc tranh cãi :)

Tuyên bố của ông Lidström là một cấu trúc shared_ptr<Base> p(new Derived);không yêu cầu Base phải có một trình hủy ảo:

Armen Tsirunyan : "Thật sao? Liệu shared_ptr dọn dẹp một cách chính xác? Bạn có thể vui lòng trong trường hợp này chứng minh hiệu ứng đó có thể được thực hiện như thế nào?"

Daniel Lidström : " shared_ptr sử dụng trình hủy của chính nó để xóa phiên bản Concrete. Đây được gọi là RAII trong cộng đồng C ++. Lời khuyên của tôi là bạn nên tìm hiểu tất cả những gì có thể về RAII. Nó sẽ giúp việc viết mã C ++ của bạn dễ dàng hơn rất nhiều khi bạn sử dụng RAII trong mọi tình huống. "

Armen Tsirunyan : "Tôi biết về RAII và tôi cũng biết rằng cuối cùng trình hủy shared_ptr có thể xóa px được lưu trữ khi pn đạt đến 0. Nhưng nếu px có con trỏ kiểu tĩnh Basevà con trỏ kiểu động đến Derived, thì trừ khi Basecó trình hủy ảo, điều này sẽ dẫn đến hành vi không xác định. Hãy sửa cho tôi nếu tôi sai. "

Daniel Lidström : " shared_ptr biết kiểu tĩnh là Bê tông. Nó biết điều này từ khi tôi chuyển nó vào hàm tạo của nó! Có vẻ hơi giống phép thuật, nhưng tôi có thể đảm bảo với bạn rằng nó là do thiết kế và cực kỳ đẹp."

Vì vậy, hãy phán xét chúng tôi. Làm thế nào để có thể (nếu có) triển khai shared_ptr mà không yêu cầu các lớp đa hình phải có hàm hủy ảo? Cảm ơn trước


3
Bạn có thể đã liên kết với chủ đề ban đầu .
Darin Dimitrov

8
Một điều thú vị khác là nó shared_ptr<void> p(new Derived)cũng sẽ phá hủy Derivedđối tượng bằng trình hủy của nó, bất kể nó có virtualhay không.
dalle

7
Cách đặt câu hỏi tuyệt vời :)
rubenvb

5
Mặc dù shared_ptr cho phép điều này, nhưng thực sự là một ý tưởng tồi nếu thiết kế một lớp làm cơ sở mà không có dtor ảo. Nhận xét của Daniel về RAII là gây hiểu lầm - không liên quan gì đến điều này - nhưng cuộc trò chuyện được trích dẫn nghe giống như một thông tin sai đơn giản (và giả định không chính xác về cách shared_ptr hoạt động).

6
Không phải RAII, mà là nó gõ xóa trình hủy. Bạn phải cẩn thận, bởi vì shared_ptr<T>( (T*)new U() )nơi struct U:Tsẽ không làm điều đúng (và điều này có thể được thực hiện gián tiếp một cách dễ dàng, chẳng hạn như một chức năng mà phải mất một T*và được truyền một U*)
Yakk - Adam Nevraumont

Câu trả lời:


74

Có, có thể triển khai shared_ptr theo cách đó. Boost không và tiêu chuẩn C ++ 11 cũng yêu cầu hành vi này. Như một sự linh hoạt được bổ sung, shared_ptr quản lý nhiều hơn một bộ đếm tham chiếu. Một bộ phân định được gọi là thường được đưa vào cùng một khối bộ nhớ cũng chứa các bộ đếm tham chiếu. Nhưng phần thú vị là loại trình xóa này không phải là một phần của loại shared_ptr. Đây được gọi là "tẩy kiểu" và về cơ bản là cùng một kỹ thuật được sử dụng để triển khai "hàm đa hình" chức năng boost :: hoặc std :: để ẩn kiểu của trình điều khiển thực tế. Để làm cho ví dụ của bạn hoạt động, chúng tôi cần một hàm tạo mẫu:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

Vì vậy, nếu bạn sử dụng điều này với các lớp Cơ sở và Có nguồn gốc ...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... hàm tạo mẫu với Y = Derived được sử dụng để xây dựng đối tượng shared_ptr. Do đó, phương thức khởi tạo có cơ hội tạo đối tượng deleter thích hợp và các bộ đếm tham chiếu và lưu trữ một con trỏ tới khối điều khiển này như một thành viên dữ liệu. Nếu bộ đếm tham chiếu bằng 0, bộ xóa nhận biết được tạo trước đó và có nguồn gốc sẽ được sử dụng để loại bỏ đối tượng.

Tiêu chuẩn C ++ 11 có những điều sau đây để nói về hàm tạo này (20.7.2.2.1):

Yêu cầu: p phải có thể chuyển đổi thành T*. Ysẽ là một loại hoàn chỉnh. Biểu thức delete pphải được hình thành tốt, phải có hành vi được xác định rõ ràng và sẽ không có ngoại lệ.

Tác dụng: xây dựng một shared_ptrđối tượng sở hữu con trỏ p.

Và đối với trình hủy (20.7.2.2.2):

Tác dụng: Nếu *thistrống hoặc cổ phiếu sở hữu với một shared_ptrví dụ ( use_count() > 1), không có tác dụng phụ. Ngược lại, if *thissở hữu một đối tượng pvà một bộ phân định d, d(p)được gọi. Ngược lại, if *thissở hữu một con trỏ p, và delete pđược gọi.

(nhấn mạnh sử dụng phông chữ đậm là của tôi).


the upcoming standard also requires this behaviour: (a) Bạn có thể vui lòng cung cấp tài liệu tham khảo (cho tiêu chuẩn) nào và (b) tiêu chuẩn nào không?
kevinarpe

Tôi chỉ muốn thêm nhận xét vào câu trả lời của @sellibitze vì tôi không có đủ điểm add a comment. IMO, nó là nhiều Boost does thishơn the Standard requires. Tôi không nghĩ Tiêu chuẩn yêu cầu điều đó từ những gì tôi đang hiểu. Nói về dụ @sellibitze 's shared_ptr<Base> sp (new Derived);, Yêu cầu của constructorchỉ yêu cầu delete Derivedlà được xác định rõ và cũng được hình thành. Đối với đặc điểm kỹ thuật của destructor, cũng có một p, nhưng tôi không nghĩ rằng nó đề cập đến pđặc điểm kỹ thuật của constructor.
Lujun Weng

28

Khi shared_ptr được tạo, nó sẽ lưu trữ một đối tượng deleter bên trong chính nó. Đối tượng này được gọi khi shared_ptr sắp giải phóng tài nguyên trỏ. Vì bạn biết cách phá hủy tài nguyên tại thời điểm xây dựng, bạn có thể sử dụng shared_ptr với các loại chưa hoàn chỉnh. Ai đã tạo shared_ptr đã lưu trữ một trình phân tách chính xác ở đó.

Ví dụ: bạn có thể tạo trình xóa tùy chỉnh:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

p sẽ gọi DeleteDerived để hủy đối tượng được trỏ. Việc triển khai thực hiện điều này tự động.


4
+1 cho nhận xét về các loại không đầy đủ, rất hữu ích khi sử dụng shared_ptrlàm thuộc tính.
Matthieu M.

16

Đơn giản,

shared_ptr sử dụng hàm deleter đặc biệt được tạo bởi hàm tạo luôn sử dụng hàm hủy của đối tượng đã cho chứ không phải hàm hủy của Base, đây là một chút công việc với lập trình meta mẫu, nhưng nó hoạt động.

Đại loại vậy

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}

1
hmm ... thú vị, Tôi bắt đầu tin rằng :) này
Armen Tsirunyan

1
@Armen Tsirunyan Bạn nên xem qua phần mô tả thiết kế của shared_ptr trước khi bắt đầu dùng đĩa. Đây 'chụp của deleter' là một trong những tính năng cần thiết của shared_ptr ...
Paul Michalik

6
@ paul_71: Tôi đồng ý với bạn. Mặt khác, tôi tin rằng cuộc thảo luận này không chỉ hữu ích cho tôi mà còn cho những người khác không biết sự thật này về shared_ptr. Vì vậy, tôi đoán nó không phải là một tội lỗi lớn để bắt đầu chủ đề này anyway :)
Armen Tsirunyan

3
@Armen Tất nhiên là không. Thay vào đó, bạn đã làm rất tốt khi chỉ ra tính năng thực sự rất quan trọng này của shared_ptr <T>, thường được giám sát ngay cả bởi các nhà phát triển c ++ có kinh nghiệm.
Paul Michalik
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.