2012-04-26 29 views
9

xem xét chương trình này:Nhiều shared_ptr lưu trữ cùng một con trỏ

#include <memory> 
#include <iostream> 

class X 
    : public std::enable_shared_from_this<X> 
{ 
public: 
    struct Cleanup1 { void operator()(X*) const; }; 
    struct Cleanup2 { void operator()(X*) const; }; 
    std::shared_ptr<X> lock1(); 
    std::shared_ptr<X> lock2(); 
}; 

std::shared_ptr<X> X::lock1() 
{ 
    std::cout << "Resource 1 locked" << std::endl; 
    return std::shared_ptr<X>(this, Cleanup1()); 
} 

std::shared_ptr<X> X::lock2() 
{ 
    std::cout << "Resource 2 locked" << std::endl; 
    return std::shared_ptr<X>(this, Cleanup2()); 
} 

void X::Cleanup1::operator()(X*) const 
{ 
    std::cout << "Resource 1 unlocked" << std::endl; 
} 

void X::Cleanup2::operator()(X*) const 
{ 
    std::cout << "Resource 2 unlocked" << std::endl; 
} 

int main() 
{ 
    std::cout << std::boolalpha; 

    X x; 
    std::shared_ptr<X> p1 = x.lock1(); 
    { 
    std::shared_ptr<X> p2 = x.lock2(); 
    } 
} 

tôi không thấy bất cứ điều gì trong C++ 11 tiêu chuẩn phần 20.7.2 cho thấy bất kỳ của việc này là không hợp lệ. Có một chút khác thường khi có hai đối tượng shared_ptr lưu trữ cùng một con trỏ &x nhưng không chia sẻ quyền sở hữu và sử dụng "dấu phân tách" không kết thúc tuổi thọ của *get(), nhưng không có gì cấm. (Và nếu một trong những người hoàn toàn ngoài ý muốn, nó sẽ rất khó để giải thích tại sao một số shared_ptr chức năng thành viên chấp nhận một giá trị std::nullptr_t.) Và như mong đợi, các kết quả đầu ra chương trình:

Resource 1 locked 
Resource 2 locked 
Resource 2 unlocked 
Resource 1 unlocked 

Nhưng bây giờ nếu tôi thêm một chút để main():

int main() 
{ 
    std::cout << std::boolalpha; 

    X x; 
    std::shared_ptr<X> p1 = x.lock1(); 
    bool test1(x.shared_from_this()); 
    std::cout << "x.shared_from_this() not empty: " << test1 << std::endl; 
    { 
    std::shared_ptr<X> p2 = x.lock2(); 
    } 
    try { 
    bool test2(x.shared_from_this()); 
    std::cout << "x.shared_from_this() not empty: " << test2 << std::endl; 
    } catch (std::exception& e) { 
    std::cout << "caught: " << e.what() << std::endl; 
    } 
} 

thì mọi thứ trở nên phức tạp hơn. Với g ++ 4.6.3, tôi nhận được kết quả:

Resource 1 locked 
x.shared_from_this() not empty: true 
Resource 2 locked 
Resource 2 unlocked 
caught: std::bad_weak_ptr 
Resource 1 unlocked 

Tại sao cuộc gọi thứ hai để shared_from_this() sẽ thất bại? Tất cả các yêu cầu của 20.7.2.4p7 được đáp ứng:

Yêu cầu:enable_shared_from_this<T> sẽ là một lớp cơ sở tiếp cận của T. *this phải là một đối tượng con của một đối tượng t loại T. Phải có ít nhất một ví dụ shared_ptrp rằng sở hữu&t.

[TX, tx, pp1.]

Nhưng g ++ 's enable_shared_from_this cơ bản sau việc thực hiện đề xuất từ ​​(không quy chuẩn) "Ghi chú" trong 20.7.2.4p10, sử dụng một tư nhân weak_ptr thành viên trong lớp enable_shared_from_this. Và dường như không thể giải thích cho loại vấn đề này mà không làm điều gì đó phức tạp hơn đáng kể trong enable_shared_from_this.

Đây có phải là lỗi trong tiêu chuẩn không? (Nếu vậy, không cần bình luận ở đây về giải pháp "nên" là gì: thêm yêu cầu để chương trình ví dụ gọi hành vi không xác định, thay đổi Ghi chú thành không thực hiện việc thực hiện đơn giản như vậy là đủ, ....)

+1

Từ quan điểm tiêu chuẩn, tôi không chắc chắn. Lý do WHY là triển khai g ++ (và boost) mong rằng lần đầu tiên bạn tạo một con trỏ được chia sẻ từ một cá thể con trỏ thô của X sẽ là thời gian duy nhất, và biến private_ptr riêng được đặt để trỏ đến cá thể đã tạo đó. Khi bạn tạo một con trỏ chia sẻ mới thứ hai trên cùng một cá thể trong 'lock2()', nó sẽ ghi đè lên weak_ptr ban đầu, và khi nó mở khóa, con trỏ yếu trỏ vào không có gì, do đó lỗi. –

+0

Ghi chú không quy chuẩn thể hiện một ví dụ thực hiện 'enable_from_this' kết luận (đoạn 11) với" Các constructor shared_ptr tạo ** con trỏ độc đáo ** có thể phát hiện sự hiện diện của một cơ sở 'enable_shared_from_this' và gán mới được tạo ra' shared_ptr' vào thành viên '__weak_this' của nó." [nhấn mạnh mỏ] Tôi thấy nó đáng chú ý rằng lưu ý này đã không được worded với một cái gì đó để tác dụng của "các nhà xây dựng tạo ra * sở hữu * con trỏ", và tôi tự hỏi những gì là một duy nhất 'shared_ptr'. –

+1

@Luc Tôi tin rằng đó là đề cập đến các nhà xây dựng sau khi tạo ra sẽ trở lại đúng từ 'duy nhất()' của họ. Về cơ bản, các nhà xây dựng có quyền sở hữu ban đầu từ một con trỏ thô hoặc một unique_ptr. –

Trả lời

4

Tôi đồng ý đây là một lỗ hổng trong đặc điểm kỹ thuật, do đó có lỗi. Về cơ bản nó giống như http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179 mặc dù vấn đề đó xuất phát từ góc hơi khác (và IMHO rõ ràng hơn là bị hỏng).

Tôi không chắc mình đồng ý rằng đây là lạm dụng shared_ptr, tôi nghĩ rằng bạn nên làm điều đó với shared_ptrs, bởi vì không giống như mã trong số 2179 bạn sử dụng các dấu phân cách không có op. Tôi nghĩ rằng vấn đề là khi bạn cố gắng kết hợp loại sử dụng shared_ptr với enable_shared_from_this.

Vì vậy, suy nghĩ đầu tiên của tôi là để sửa chữa nó bằng cách mở rộng các yêu cầu của shared_from_this:

Yêu cầu:enable_shared_from_this<T> sẽ là một lớp cơ sở tiếp cận của T. *this phải là một đối tượng con của một đối tượng t loại T. Sẽ có ít nhất một shared_ptr dụ p rằng sở hữu&tvà bất kỳ shared_ptr trường hợp khác mà sở hữu &t phải chia sẻ quyền sở hữu với p.

Đây không phải là khá đầy đủ tuy nhiên, vì dụ của bạn đáp ứng yêu cầu rằng: tại cuộc gọi thứ hai để shared_from_this() chỉ có một chủ sở hữu (p1) nhưng bạn đã "hỏng" trạng thái của lớp cơ sở enable_shared_from_this bằng cách gọi số lock2().

Một hình thức nhỏ của chương trình là:

#include <memory> 
using namespace std; 

int main() 
{ 
    struct X : public enable_shared_from_this<X> { }; 
    auto xraw = new X; 
    shared_ptr<X> xp1(xraw); // #1 
    { 
    shared_ptr<X> xp2(xraw, [](void*) { }); // #2 
    } 
    xraw->shared_from_this(); // #3 
} 

Cả ba libstdC++, libC++ và VC++ (Dinkumware) cư xử giống nhau và ném bad_weak_ptr ở vị trí thứ 3, vì ở vị trí thứ 2 họ cập nhật weak_ptr<X> viên của các lớp cơ sở để làm cho nó chia sẻ quyền sở hữu với xp2, mà đi ra khỏi phạm vi rời khỏi weak_ptr<X> ở trạng thái hết hạn.

Thú vị boost::shared_ptr không ném, thay vào đó # 2 là số no-op và # 3 trả về số shared_ptr chia sẻ quyền sở hữu với xp1. Điều này được thực hiện để đáp ứng với một số bug report với gần như chính xác ví dụ giống như ví dụ trên.

3

Có, có lỗi ở đây trong C++ 11. Trong việc cho phép này:

Đó là một chút khác thường có hai đối tượng shared_ptr lưu trữ các con trỏ cùng & x nhưng không chia sẻ quyền sở hữu và sử dụng "deleters" mà không chấm dứt cuộc đời của * get(), nhưng không có gì cấm nó.

Điều này nên được quy định rõ ràng là hành vi không xác định, bất kể về những gì "các đường" thực hiện. Chắc chắn, nó có thể là kỹ thuật không bất hợp pháp để làm những việc theo cách đó.

Tuy nhiên, bạn là nằm cho những người sử dụng mã.Kỳ vọng của bất kỳ ai nhận được shared_ptr là bây giờ họ có quyền sở hữu đối tượng. Vì vậy, miễn là chúng giữ được shared_ptr (hoặc một bản sao của chúng) xung quanh, đối tượng nó trỏ đến vẫn sẽ tồn tại.

Đó không phải là trường hợp với mã của bạn. Vì vậy, tôi sẽ nói rằng nó là cú pháp chính xác nhưng ngữ nghĩa không hợp lệ.

Ngôn ngữ cho shared_from_this là tốt. Đó là ngôn ngữ cho shared_ptr cần thay đổi. Nó nên nói rằng nó là hành vi không xác định để tạo ra hai con trỏ độc đáo riêng biệt mà "sở hữu" cùng một con trỏ.

+0

Là' shared_ptr p1 (nullptr, d1); shared_ptr p2 (nullptr, d2); 'chỉ là không hợp lệ? – aschepler

+0

Nếu tôi cung cấp thời gian lưu trữ tĩnh 'x' và làm cho ctor và dtor của' X' là riêng tư thì sao? Sau đó, tôi thực sự có thể đảm bảo rằng nếu bạn có một không có sản phẩm nào 'shared_ptr ', đối tượng nó trỏ đến vẫn còn tồn tại (vì nó luôn luôn tồn tại). – aschepler

+0

Không cố gắng để bắn hạ này: Tôi nghĩ rằng ý tưởng cơ bản sẽ làm cho một sửa chữa tốt cho tiêu chuẩn, nhưng nó sẽ cần một số từ ngữ cẩn thận hơn. – aschepler

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