Ví dụ để sử dụng shared_ptr?


82

Xin chào, tôi đã hỏi một câu hỏi hôm nay về Cách chèn các loại đối tượng khác nhau trong cùng một mảng vectơ và mã của tôi trong câu hỏi đó là

 gate* G[1000];
G[0] = new ANDgate() ;
G[1] = new ORgate;
//gate is a class inherited by ANDgate and ORgate classes
class gate
{
 .....
 ......
 virtual void Run()
   {   //A virtual function
   }
};
class ANDgate :public gate 
  {.....
   .......
   void Run()
   {
    //AND version of Run
   }  

};
 class ORgate :public gate 
  {.....
   .......
   void Run()
   {
    //OR version of Run
   }  

};      
//Running the simulator using overloading concept
 for(...;...;..)
 {
  G[i]->Run() ;  //will run perfectly the right Run for the right Gate type
 } 

và tôi muốn sử dụng vectơ nên ai đó đã viết rằng tôi nên làm như vậy:

std::vector<gate*> G;
G.push_back(new ANDgate); 
G.push_back(new ORgate);
for(unsigned i=0;i<G.size();++i)
{
  G[i]->Run();
}

nhưng sau đó anh ấy và nhiều người khác gợi ý rằng tôi nên sử dụng vùng chứa con trỏ Boost
hoặc shared_ptr. Tôi đã dành 3 giờ qua để đọc về chủ đề này, nhưng tài liệu có vẻ khá nâng cao đối với tôi. **** Bất cứ ai có thể cho tôi một ví dụ mã nhỏ về shared_ptrcách sử dụng và lý do tại sao họ đề xuất sử dụng shared_ptr. Ngoài ra còn có các loại khác như ptr_vector, ptr_listptr_deque**

Chỉnh sửa1: Tôi cũng đã đọc một ví dụ mã bao gồm:

typedef boost::shared_ptr<Foo> FooPtr;
.......
int main()
{
  std::vector<FooPtr>         foo_vector;
........
FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
...........
}

Và tôi không hiểu cú pháp!


2
Bạn không hiểu cú pháp nào? Dòng đầu tiên của maintạo một vectơ có thể chứa các con trỏ dùng chung đến một kiểu được gọi là Foo; cái thứ hai tạo một con trỏ Foosử dụng newvà dùng chung để quản lý nó; thứ ba đặt một bản sao của con trỏ dùng chung vào vector.
Mike Seymour

Câu trả lời:


116

Sử dụng a vectorof shared_ptrsẽ loại bỏ khả năng rò rỉ bộ nhớ vì bạn quên đi theo vector và gọi deletetừng phần tử. Hãy xem qua một phiên bản được sửa đổi một chút của dòng ví dụ.

typedef boost::shared_ptr<gate> gate_ptr;

Tạo bí danh cho loại con trỏ dùng chung. Điều này tránh sự xấu xí trong ngôn ngữ C ++ dẫn đến việc nhập std::vector<boost::shared_ptr<gate> >và quên khoảng cách giữa các dấu đóng lớn hơn .

    std::vector<gate_ptr> vec;

Tạo một vector rỗng của boost::shared_ptr<gate>các đối tượng.

    gate_ptr ptr(new ANDgate);

Phân bổ một phiên bản mới ANDgatevà lưu trữ nó vào một shared_ptr. Lý do để làm điều này một cách riêng biệt là để ngăn chặn sự cố có thể xảy ra nếu một hoạt động ném. Điều này là không thể trong ví dụ này. Các Boost shared_ptr"Thực tiễn tốt nhất" giải thích lý do tại sao nó là một thực hành tốt nhất để phân bổ vào một đối tượng tự do đứng thay vì một tạm thời.

    vec.push_back(ptr);

Điều này tạo ra một con trỏ được chia sẻ mới trong vectơ và sao chép ptrvào đó. Việc đếm tham chiếu trong ruột của shared_ptrđảm bảo rằng đối tượng được cấp phát bên trong của ptrđược chuyển một cách an toàn vào vector.

Điều không được giải thích là hàm hủy để shared_ptr<gate>đảm bảo rằng bộ nhớ được cấp phát sẽ bị xóa. Đây là nơi tránh rò rỉ bộ nhớ. Hàm hủy for std::vector<T>đảm bảo rằng hàm hủy đối Tvới mọi phần tử được lưu trữ trong vectơ. Tuy nhiên, bộ hủy cho một con trỏ (ví dụ gate*:) không xóa bộ nhớ mà bạn đã cấp phát . Đó là những gì bạn đang cố gắng tránh bằng cách sử dụng shared_ptrhoặc ptr_vector.


1
Đó là chi tiết :). Câu hỏi của tôi liên quan đến dòng thứ 3 của mã gate_ptr ptr (ANDgate mới); Nó không cảm thấy quen thuộc với tôi, ptr của một loại cổng con trỏ chia sẻ và sau đó giữa các dấu ngoặc nhọn, bạn đã gửi một ANDgate mới! Thật là khó hiểu.
Ahmed

6
@Ahmed: biểu thức tổng thể là một khởi tạo biến, giống như bạn có thể viết int x(5);để khởi tạo xvới giá trị 5. Trong trường hợp này, nó được khởi tạo với giá trị của biểu thức mới tạo ra một ANDgate; giá trị của biểu thức mới là một con trỏ đến đối tượng mới.
Mike Seymour

42

Tôi sẽ thêm rằng một trong những điều quan trọng về shared_ptr's là chỉ bao giờ xây dựng chúng với cú pháp sau:

shared_ptr<Type>(new Type(...));

Bằng cách này, con trỏ "thực" Typeđược ẩn danh trong phạm vi của bạn và chỉ được giữ bởi con trỏ được chia sẻ. Vì vậy, bạn sẽ không thể vô tình sử dụng con trỏ "thực" này. Nói cách khác, không bao giờ làm điều này:

Type* t_ptr = new Type(...);
shared_ptr<Type> t_sptr ptrT(t_ptr);
//t_ptr is still hanging around!  Don't use it!

Mặc dù điều này sẽ hoạt động, nhưng bây giờ bạn có một Type*con trỏ ( t_ptr) trong hàm của bạn, nó nằm bên ngoài con trỏ được chia sẻ. Thật nguy hiểm khi sử dụng ở t_ptrbất cứ đâu, bởi vì bạn không bao giờ biết khi nào con trỏ dùng chung chứa nó có thể hủy nó và bạn sẽ mặc định.

Tương tự với các con trỏ do các lớp khác trả lại cho bạn. Nếu một lớp học mà bạn không viết cho bạn một con trỏ, nói chung sẽ không an toàn nếu chỉ đặt nó vào a shared_ptr. Không trừ khi bạn chắc chắn rằng lớp không còn sử dụng đối tượng đó nữa. Bởi vì nếu bạn đặt nó vào a shared_ptr, và nó nằm ngoài phạm vi, đối tượng sẽ được giải phóng khi lớp có thể vẫn cần nó.


8
Tất cả những gì Ken nói đều tốt và đúng, nhưng tôi tin rằng cách ưa thích để gọi nó bây giờ là auto t_ptr = make_shared<Type>(...);hoặc tương đương shared_ptr<Type> t_ptr = make_shared<Type>(...);, đơn giản vì hình thức đó hiệu quả hơn.
Jason Sydes

@KenSimon, có phải có dấu phẩy ,giữa t_sptrptrTtrong shared_ptr<Type> t_sptr ptrT(t_ptr);không?
Allanqunzi

Ngoài sự mơ hồ trong mã ví dụ, cảnh báo tốt - nhưng bạn phải làm cho nó xấu hổ, vì dạng 1 sạch hơn rất nhiều và có lẽ quan trọng hơn, chắc chắn bất kỳ ai sử dụng con trỏ thông minh đều biết rằng nó tồn tại chính xác để tránh gặp phải những nguyên con trỏ nổi xung quanh. Đoạn cuối thật thú vị; rất may là tôi vẫn chưa làm việc với bất kỳ thư viện nào buộc tôi phải sử dụng các điểm thuộc loại thô hoặc không rõ ràng, mặc dù tôi chắc chắn rằng điều đó sẽ xảy ra một lúc nào đó.
underscore_d

20

Theo tôi, học cách sử dụng con trỏ thông minh là một trong những bước quan trọng nhất để trở thành một lập trình viên C ++ có năng lực. Như bạn biết bất cứ khi nào bạn tạo một đối tượng tại một thời điểm nào đó bạn muốn xóa nó.

Một vấn đề nảy sinh là với các ngoại lệ, có thể rất khó để đảm bảo một đối tượng luôn được giải phóng chỉ một lần trong tất cả các đường dẫn thực thi có thể.

Đây là lý do của RAII: http://en.wikipedia.org/wiki/RAII

Tạo một lớp trợ giúp với mục đích đảm bảo rằng một đối tượng luôn bị xóa một lần trong tất cả các đường dẫn thực thi.

Ví dụ về một lớp như thế này là: std :: auto_ptr

Nhưng đôi khi bạn thích chia sẻ đồ vật với người khác. Nó chỉ nên được xóa khi không còn sử dụng nó nữa.

Để trợ giúp cho việc đó, các chiến lược đếm tham chiếu đã được phát triển nhưng bạn vẫn cần nhớ addref và giải phóng ref theo cách thủ công. Về bản chất đây là vấn đề tương tự như new / delete.

Đó là lý do tại sao boost đã phát triển boost :: shared_ptr, đó là con trỏ thông minh đếm tham chiếu để bạn có thể chia sẻ các đối tượng và không bị rò rỉ bộ nhớ ngoài ý muốn.

Với việc bổ sung C ++ tr1, tiêu chuẩn này cũng được thêm vào tiêu chuẩn c ++ nhưng được đặt tên là std :: tr1 :: shared_ptr <>.

Tôi khuyên bạn nên sử dụng con trỏ chia sẻ tiêu chuẩn nếu có thể. ptr_list, ptr_dequeue và các vùng chứa IIRC chuyên dụng cho các loại con trỏ cũng vậy. Tôi bỏ qua chúng cho bây giờ.

Vì vậy, chúng tôi có thể bắt đầu từ ví dụ của bạn:

std::vector<gate*> G; 
G.push_back(new ANDgate);  
G.push_back(new ORgate); 
for(unsigned i=0;i<G.size();++i) 
{ 
  G[i]->Run(); 
} 

Vấn đề ở đây là bây giờ bất cứ khi nào G vượt ra ngoài phạm vi, chúng ta sẽ rò rỉ 2 đối tượng được thêm vào G. Hãy viết lại nó để sử dụng std :: tr1 :: shared_ptr

// Remember to include <memory> for shared_ptr
// First do an alias for std::tr1::shared_ptr<gate> so we don't have to 
// type that in every place. Call it gate_ptr. This is what typedef does.
typedef std::tr1::shared_ptr<gate> gate_ptr;    
// gate_ptr is now our "smart" pointer. So let's make a vector out of it.
std::vector<gate_ptr> G; 
// these smart_ptrs can't be implicitly created from gate* we have to be explicit about it
// gate_ptr (new ANDgate), it's a good thing:
G.push_back(gate_ptr (new ANDgate));  
G.push_back(gate_ptr (new ORgate)); 
for(unsigned i=0;i<G.size();++i) 
{ 
   G[i]->Run(); 
} 

Khi G vượt ra khỏi phạm vi, bộ nhớ sẽ tự động được lấy lại.

Như một bài tập mà tôi đã làm phiền những người mới trong nhóm của tôi là yêu cầu họ viết lớp con trỏ thông minh của riêng họ. Sau đó, sau khi bạn hoàn tất, hãy loại bỏ lớp học ngay lập tức và không bao giờ sử dụng nó nữa. Hy vọng rằng bạn đã có được kiến ​​thức quan trọng về cách hoạt động của con trỏ thông minh. Không có ma thuật thực sự.


Người hướng dẫn của tôi đã cho tôi một lời khuyên tương tự về việc viết các lớp học của riêng tôi vì vậy tôi sẽ thử điều đó cho chắc chắn. TY.
Ahmed

bạn nên sử dụng một trình lặp để chạy tất cả các cổngfor( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }
Guillaume Massé

1
Hoặc tốt hơn mới "foreach" trong C ++
Just another metaprogrammer

2

Tài liệu tăng cường cung cấp một ví dụ bắt đầu khá tốt: ví dụ shared_ptr (thực sự là về một vectơ của con trỏ thông minh) hoặc tài liệu shared_ptr Câu trả lời sau đây của Johannes Schaub giải thích khá tốt về con trỏ thông minh tăng cường: con trỏ thông minh giải thích

Ý tưởng đằng sau (càng ít từ càng tốt) ptr_vector là nó xử lý việc phân bổ bộ nhớ đằng sau các con trỏ được lưu trữ cho bạn: giả sử bạn có một vectơ con trỏ như trong ví dụ của bạn. Khi thoát ứng dụng hoặc rời khỏi phạm vi mà vectơ được xác định, bạn sẽ phải tự dọn dẹp (bạn đã cấp phát động ANDgate và ORgate) nhưng chỉ xóa vectơ sẽ không thực hiện được vì vectơ đang lưu trữ các con trỏ và không phải các đối tượng thực tế (nó sẽ không phá hủy nhưng những gì nó chứa).

 // if you just do
 G.clear() // will clear the vector but you'll be left with 2 memory leaks
 ...
// to properly clean the vector and the objects behind it
for (std::vector<gate*>::iterator it = G.begin(); it != G.end(); it++)
{
  delete (*it);
}

boost :: ptr_vector <> sẽ xử lý phần trên cho bạn - nghĩa là nó sẽ phân bổ bộ nhớ đằng sau các con trỏ mà nó lưu trữ.


shared_ptr là một con trỏ thông minh - một "trình bao bọc" sáng bóng cho một con trỏ đơn giản, giả sử thêm một số AI vào một loại con trỏ. ptr_vector là một vùng chứa thông minh cho các con trỏ - một "trình bao bọc" cho một vùng chứa các con trỏ.
celavek

vì vậy ptr_vector là một loại thay thế cho vector bình thường?
Ahmed

@Ahmed Tôi đoán bạn có thể nghĩ về nó như vậy.
celavek

2

Thông qua Boost, bạn có thể làm điều đó>

std::vector<boost::any> vecobj;
    boost::shared_ptr<string> sharedString1(new string("abcdxyz!"));    
    boost::shared_ptr<int> sharedint1(new int(10));
    vecobj.push_back(sharedString1);
    vecobj.push_back(sharedint1);

> để chèn loại đối tượng khác nhau trong vùng chứa vectơ của bạn. trong khi để truy cập, bạn phải sử dụng any_cast, hoạt động giống như dynamic_cast, hy vọng nó sẽ hoạt động theo nhu cầu của bạn.


1
#include <memory>
#include <iostream>

class SharedMemory {
    public: 
        SharedMemory(int* x):_capture(x){}
        int* get() { return (_capture.get()); }
    protected:
        std::shared_ptr<int> _capture;
};

int main(int , char**){
    SharedMemory *_obj1= new SharedMemory(new int(10));
    SharedMemory *_obj2 = new SharedMemory(*_obj1);
    std::cout << " _obj1: " << *_obj1->get() << " _obj2: " << *_obj2->get()
    << std::endl;
    delete _obj2;

    std::cout << " _obj1: " << *_obj1->get() << std::endl;
    delete _obj1;
    std::cout << " done " << std::endl;
}

Đây là một ví dụ về shared_ptr đang hoạt động. _obj2 đã bị xóa nhưng con trỏ vẫn còn giá trị. đầu ra là, ./test _obj1: 10 _obj2: 10 _obj2: 10 done


0

Cách tốt nhất để thêm các đối tượng khác nhau vào cùng một vùng chứa là sử dụng vòng lặp dựa trên make_shared, vectơ và phạm vi và bạn sẽ có một mã đẹp, rõ ràng và "dễ đọc"!

typedef std::shared_ptr<gate> Ptr   
vector<Ptr> myConatiner; 
auto andGate = std::make_shared<ANDgate>();
myConatiner.push_back(andGate );
auto orGate= std::make_shared<ORgate>();
myConatiner.push_back(orGate);

for (auto& element : myConatiner)
    element->run();
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.