2015-02-19 15 views
7

Từ http://en.cppreference.com/w/cpp/memory/unique_ptr: lớpLý do cơ bản cho sự khác biệt về hành vi hủy diệt giữa std :: unique_ptr và std :: shared_ptr là gì?

Nếu T có nguồn gốc (sic) của một số cơ sở B, sau đó std::unique_ptr<T> là ngầm mui trần để std::unique_ptr<B>. Trình gỡ rối mặc định của kết quả std::unique_ptr<B> sẽ sử dụng toán tử xóa cho B, dẫn đến hành vi không xác định trừ khi hàm hủy của B là ảo. Lưu ý rằng std::shared_ptr hoạt động khác: std::shared_ptr<B> sẽ sử dụng toán tử xóa cho loại T và đối tượng được sở hữu sẽ bị xóa ngay cả khi hủy của B không phải là ảo.

Lý do cho sự khác biệt về hành vi khi tiêu hủy được mô tả ở trên là gì? Dự đoán ban đầu của tôi sẽ là hiệu suất?

Cũng thú vị khi biết là làm thế nào một std::shared_ptr<B> có thể gọi destructor của một loại T trong trường hợp destructor trên B là không ảo và không thể được gọi như xa như tôi có thể nhìn thấy từ bối cảnh std::shared_ptr<B>?

+1

Điều này có thể hữu ích: http://stackoverflow.com/questions/6876751/differences-between-unique-ptr-and-shared-ptr –

Trả lời

8

std::shared_ptr<X> đã có một loạt chi phí trên một số liệu B*.

Một shared_ptr<X> về cơ bản duy trì 4 điều. Nó duy trì một con trỏ-to- B, nó duy trì hai tội tham chiếu (một số tài liệu tham khảo "cứng" và "mềm" một cho weak_ptr), và nó duy trì một hàm dọn dẹp.

Chức năng dọn dẹp là lý do tại sao shared_ptr<X> cư xử khác nhau. Khi bạn tạo một shared_ptr<X>, một chức năng mà các cuộc gọi mà đặc biệt loại của destructor được tạo ra và lưu trữ trong các chức năng dọn dẹp bởi shared_ptr<X> quản lý.

Khi bạn thay đổi loại được quản lý (B* trở thành C*), chức năng dọn dẹp sẽ không thay đổi.

shared_ptr<X> cần quản lý số lượng tham chiếu, chi phí bổ sung của bộ nhớ chức năng dọn dẹp đó là biên.

Đối với một số unique_ptr<B>, lớp này gần như rẻ với giá trị B*. Nó duy trì trạng thái zero khác với B* của nó, và hành vi của nó (tại hủy diệt) xuống đến if (b) delete b;. (Có, rằng if (b) là dư thừa, nhưng trình tối ưu hóa có thể tìm ra điều đó).

Để hỗ trợ cast-to-base và xóa-như-có nguồn gốc, tiểu bang thêm sẽ phải được lưu trữ mà nhớ unique_ptrthực sự đến một lớp dẫn xuất. Điều này có thể ở dạng của một con trỏ được lưu trữ-to-deleter, giống như một shared_ptr.

Đó sẽ, tuy nhiên, tăng gấp đôi kích thước của một unique_ptr<B>, hoặc yêu cầu nó để lưu trữ dữ liệu trên đống ở đâu đó.

Nó đã được quyết định rằng unique_ptr<B> nên là số không trên không, và như vậy nó không hỗ trợ cast-to-base trong khi vẫn gọi destructor của cơ sở.

Bây giờ, bạn có thể dạy unique_ptr<B> để làm điều này bằng cách thêm một loại deleter và lưu trữ một chức năng hủy diệt biết loại điều nó đang phá hủy. Ở trên đã nói về các deleter mặc định của unique_ptr, đó là không quốc tịch và tầm thường.

struct deleter { 
    void* state; 
    void(*f)(void*); 
    void operator()(void*)const{if (f) f(state);} 
    deleter(deleter const&)=default; 
    deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; } 
    deleter()=delete; 
    template<class T> 
    deleter(T*t): 
    state(t), 
    f([](void*p){delete static_cast<T*>(p);}) 
    {} 
}; 
template<class T> 
using smart_unique_ptr = std::unique_ptr<T, deleter>; 

template<class T, class...Args> 
smart_unique_ptr<T> make_smart_unique(Args&&... args) { 
    T* t = new T(std::forward<Args>(args)...); 
    return { t, t }; 
} 

live example, nơi tôi tạo ra một ptr duy nhất có nguồn gốc, lưu trữ nó trong một duy nhất-ptr đến cơ sở, và sau đó đặt lại cơ sở. Con trỏ có nguồn gốc bị xóa.

(A void(*)(void*) deleter đơn giản có thể chạy vào các vấn đề trong đó thông qua vào void* sẽ khác nhau về giá trị giữa các cơ sở và các trường hợp có nguồn gốc.)

Lưu ý rằng việc thay đổi con trỏ lưu trữ trong đó một unique_ptr mà không thay đổi deleter sẽ cho kết quả trong hành vi bị bệnh.

+0

Câu trả lời hay, nhưng 'unique_ptr' không thực sự bằng không trong toàn bộ trường hợp chung . Nó có để lưu trữ một deleter quá. Không giống 'shared_ptr', kiểu deleter là một phần của kiểu của nó (thông qua một tham số mẫu). Đối với các delet có kích thước bằng không, chi phí trên không thực sự có thể thông qua tối ưu hóa cơ sở trống. – Angew

+1

Và 'if (b) delete b;' là chính xác. – Angew

+0

@Angew Tôi luôn quên nếu nó thực hiện thử nghiệm hay không. Tôi nghĩ rằng tôi đã nhìn thấy một thực hiện mà làm điều sai trái ở đó. – Yakk

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