Câu hỏi shared_ptr magic :)


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

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

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

Daniel Lidström: "Các shared_ptr sử dụng hàm hủy riêng của nó để xóa cá thể Concrete. Điều này được gọi là RAII trong cộng đồng C ++. Lời khuyên của tôi là bạn học tất cả những gì bạn có thể về RAII. Nó sẽ làm cho mã C ++ của bạn dễ dàng hơn 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 shared_ptr destructor có thể xóa px được lưu trữ khi pn đạt 0. Nhưng nếu px có con trỏ kiểu tĩnh Base và con trỏ kiểu động Derived, sau đó trừ khi Base có một destructor ảo, điều này sẽ dẫn đến hành vi không xác định. Đúng nếu tôi đã sai lầm."

Daniel Lidström: "Các shared_ptr biết loại tĩnh là Concrete. Nó biết điều này kể từ khi tôi thông qua nó trong constructor của nó! Có vẻ hơi giống ma thuật, nhưng tôi có thể đảm bảo với bạn đó là thiết kế và cực kỳ tốt đẹp. "

Vì vậy, đánh giá chúng tôi. Làm thế nào là nó có thể (nếu nó là) để thực hiện shared_ptr mà không yêu cầu các lớp đa hình để có destructor ảo? Cảm ơn trước


76
2017-10-10 09:41


gốc


Bạn có thể đã liên kết với chủ đề ban đầu. - Darin Dimitrov
Một điều thú vị nữa là shared_ptr<void> p(new Derived) cũng sẽ phá hủy Derived đối tượng của nó là destructor, bất kể nếu nó là virtual hay không. - dalle
Cách tuyệt vời để đặt câu hỏi :) - rubenvb
Mặc dù shared_ptr cho phép điều này, đó là ý tưởng thực sự tồi để thiết kế một lớp như là một cơ sở mà không có một dtor ảo. Những bình luận của Daniel về RAII là gây hiểu lầm - nó không liên quan gì đến điều này - nhưng cuộc nói chuyện được trích dẫn có vẻ như một sự hiểu lầm đơn giản (và giả định không chính xác cách thức hoạt động của shared_ptr).
Không phải RAII, mà là loại bỏ các destructor. Bạn phải cẩn thận, bởi vì shared_ptr<T>( (T*)new U() ) Ở đâu struct U:T sẽ 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 hàm cần T* và được thông qua U*) - Yakk - Adam Nevraumont


Các câu trả lời:


Có, có thể thực hiện shared_ptr theo cách đó. Boost 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 bổ sung shared_ptr quản lý nhiều hơn chỉ là một bộ đếm tham chiếu. Một cái gọi là deleter thường được đặt 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 của deleter này không phải là một phần của kiểu shared_ptr. Điều này được gọi là "loại tẩy xoá" và về cơ bản là cùng một kỹ thuật được sử dụng để thực hiện chức năng "đa hình chức năng" tăng :: chức năng hoặc std :: chức năng để ẩn loại functor thực tế. Để làm cho ví dụ của bạn hoạt động, chúng ta cần một hàm tạo khuôn 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 học của bạn cơ sở và có nguồn gốc ...

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

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

... constructor được tạo khuôn mẫu với Y = Derived được sử dụng để xây dựng đối tượng shared_ptr. Do đó, hàm 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 làm thành viên dữ liệu. Nếu bộ đếm tham chiếu đạt đến số không, thì deleter được tạo ra trước đó và nhận biết được nhận biết sẽ được sử dụng để vứt 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):

Đòi hỏi:  p phải được chuyển đổi thành T*. Y sẽ là một loại hoàn chỉnh. Cách diễn đạt delete p được hình thành tốt, có hành vi được xác định rõ ràng và không được ném ngoại lệ.

Hiệu ứng: Xây dựng một shared_ptr vật cái đó sở hữu con trỏ p.

Và đối với destructor (20.7.2.2.2):

Hiệu ứng: Nếu *this Là trống hoặc chia sẻ quyền sở hữu với người khác shared_ptr ví dụ (use_count() > 1), Không có tác dụng phụ.   Nếu không, nếu *this sở hữu một đối tượng p và một deleter d, d(p) được gọi là.    Nếu không, nếu *this sở hữu một con trỏ pdelete p được gọi là.

(nhấn mạnh bằng chữ đậm là của tôi).


67
2017-10-10 11:18



1 cho đoạn mã để giới thiệu loại tẩy xoá xảy ra. - legends2k
the upcoming standard also requires this behaviour: (a) Tiêu chuẩn nào và (b) bạn có thể cung cấp một tham chiếu (theo tiêu chuẩn) không? - kevinarpe


Khi shared_ptr được tạo, nó lưu trữ deleter đối tượng 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 được trỏ. Vì bạn biết cách phá hủy tài nguyên tại điểm xây dựng, bạn có thể sử dụng shared_ptr với các kiểu không đầy đủ. Bất cứ ai tạo shared_ptr đều lưu trữ một deleter đúng ở đó.

Ví dụ, bạn có thể tạo một deleter 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 để tiêu diệt đối tượng nhọn. Việc triển khai thực hiện điều này tự động.


26
2017-10-10 09:47



1 cho nhận xét về các loại không đầy đủ, rất tiện dụng khi sử dụng shared_ptr như một thuộc tính. - Matthieu M.


Đơ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 destructor của đối tượng đã cho và không phải là destructor của Base, đây là một chút công việc với lập trình meta template, nhưng nó hoạt động.

Một cái gì đó như thế

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

14
2017-10-10 09:46



hmm ... thú vị, tôi bắt đầu tin điều này :) - Armen Tsirunyan
@Armen Tsirunyan Bạn nên nhìn vào mô tả thiết kế của shared_ptr trước khi bắt đầu thảo luận. Điều này 'nắm bắt của deleter' là một trong những tính năng cần thiết của shared_ptr ... - Paul Michalik
@ 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 hữu ích không chỉ đối với tôi, mà còn cho những người khác không biết thực 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 thread này anyway :) - Armen Tsirunyan
@Armen Tất nhiên là không. Thay vào đó, bạn đã thực hiện một công việc tốt trong việc trỏ đến tính năng rất quan trọng này của shared_ptr <T> thường xuyên được giám sát bởi các nhà phát triển có kinh nghiệm. - Paul Michalik