2012-02-27 25 views
15

Với C++ 11 ra khỏi đó, tôi đã tự hỏi bản thân mình nếu có một sự thay thế của boost :: ptr_containers trong C++ 11. Tôi biết tôi có thể sử dụng ví dụ a std::vector<std::unique_ptr<T> >, nhưng tôi không chắc liệu đây có phải là sự thay thế hoàn toàn không. Cách đề nghị xử lý những trường hợp này là gì?stl container với std :: unique_ptr's vs boost :: ptr_container

+2

Với 'unique_ptr', bạn vẫn phải dereference các nút, nhưng khác hơn là họ nên hành xử khá giống nhau. –

Trả lời

19

Chúng thực sự giải quyết hai vấn đề tương tự nhưng khác nhau.

Vùng chứa con trỏ là một cách để lưu trữ các đối tượng trong vùng chứa mà chỉ xảy ra như là con trỏ tới bộ nhớ được cấp phát thay vì giá trị. Họ làm tất cả mọi thứ trong quyền lực của họ để ẩn thực tế là chúng là một vùng chứa con trỏ. Điều này có nghĩa là:

  • Mục nhập trong vùng chứa không thể là NULL.
  • Giá trị bạn nhận được từ các trình lặp và chức năng là tài liệu tham khảo đối với loại, không trỏ đến loại.
  • Làm việc với nhiều thuật toán chuẩn có thể ... phức tạp. Và bởi "khéo léo", tôi có nghĩa là bị hỏng. Thùng chứa con trỏ có các thuật toán tích hợp của riêng chúng.

Tuy nhiên, thực tế là các thùng chứa con trỏ biết rằng họ đang container của con trỏ, họ có thể cung cấp một số chức năng mới:

  • Một chức năng clone thành viên đó thực hiện một bản sao sâu, thông qua việc sử dụng của một khái niệm "Cloneable" nhất định về loại đối tượng.
  • Khả năng của thùng chứa để giải phóng quyền sở hữu đối tượng của nó (ví dụ như bản sao nông).
  • Chức năng tích hợp để chuyển quyền sở hữu cho các vùng chứa khác.

Chúng thực sự là các khái niệm khá khác nhau. Có rất nhiều thứ bạn sẽ phải làm bằng tay mà container con trỏ có thể làm tự động với các chức năng chuyên biệt.

Nếu bạn thực sự cần một hộp chứa con trỏ, thì bạn có thể sử dụng vùng chứa unique_ptr. Nhưng nếu bạn cần lưu trữ một loạt các đối tượng mà bạn tình cờ phân bổ, và bạn muốn chơi các trò chơi đặc biệt với chúng liên quan đến quyền sở hữu và như vậy, thì các thùng chứa con trỏ không phải là một ý tưởng tồi.

+1

Tôi đoán bạn có thể nói rằng một trong số đó rõ ràng là một container con trỏ trong khi cái kia là một container dành cho các đối tượng đa hình ... – Mehrdad

22

Tôi quyết định viết một chương trình ngắn đưa một vài đối tượng đa hình vào một vùng chứa (bằng con trỏ tới đống), và sau đó sử dụng vùng chứa đó bằng thuật toán std ::. Tôi đã chọn std::remove_if như một ví dụ.

Sau đây là cách tôi sẽ làm điều đó với vector<unique_ptr<T>>:

#include <vector> 
#include <memory> 
#include <iostream> 

class Animal 
{ 
public: 
    Animal() = default; 
    Animal(const Animal&) = delete; 
    Animal& operator=(const Animal&) = delete; 
    virtual ~Animal() = default; 

    virtual void speak() const = 0; 
}; 

class Cat 
    : public Animal 
{ 
public: 
    virtual void speak() const {std::cout << "Meow\n";} 
    virtual ~Cat() {std::cout << "destruct Cat\n";} 
}; 

class Dog 
    : public Animal 
{ 
public: 
    virtual void speak() const {std::cout << "Bark\n";} 
    virtual ~Dog() {std::cout << "destruct Dog\n";} 
}; 

class Sheep 
    : public Animal 
{ 
public: 
    virtual void speak() const {std::cout << "Baa\n";} 
    virtual ~Sheep() {std::cout << "destruct Sheep\n";} 
}; 

int main() 
{ 
    typedef std::unique_ptr<Animal> Ptr; 
    std::vector<Ptr> v; 
    v.push_back(Ptr(new Cat)); 
    v.push_back(Ptr(new Sheep)); 
    v.push_back(Ptr(new Dog)); 
    v.push_back(Ptr(new Sheep)); 
    v.push_back(Ptr(new Cat)); 
    v.push_back(Ptr(new Dog)); 
    for (auto const& p : v) 
     p->speak(); 
    std::cout << "Remove all sheep\n"; 
    v.erase(
     std::remove_if(v.begin(), v.end(), 
         [](Ptr& p) 
          {return dynamic_cast<Sheep*>(p.get());}), 
     v.end()); 
    for (auto const& p : v) 
     p->speak(); 
} 

đầu ra này:

Meow 
Baa 
Bark 
Baa 
Meow 
Bark 
Remove all sheep 
destruct Sheep 
destruct Sheep 
Meow 
Bark 
Meow 
Bark 
destruct Dog 
destruct Cat 
destruct Dog 
destruct Cat 

mà có vẻ tốt với tôi. Tuy nhiên tôi thấy dịch này để ptr_vector có vấn đề:

boost::ptr_vector<Animal> v; 
v.push_back(new Cat); 
v.push_back(new Sheep); 
v.push_back(new Dog); 
v.push_back(new Sheep); 
v.push_back(new Cat); 
v.push_back(new Dog); 
for (auto const& p : v) 
    p.speak(); 
std::cout << "Remove all sheep\n"; 
v.erase(
    std::remove_if(v.begin(), v.end(), 
        [](Animal& p) 
         {return dynamic_cast<Sheep*>(&p);}), 
    v.end()); 
for (auto const& p : v) 
    p.speak(); 

algorithm:1897:26: error: overload resolution selected deleted operator '=' 
       *__first = _VSTD::move(*__i); 
       ~~~~~~~~^~~~~~~~~~~~~~~~~~ 
test.cpp:75:9: note: in instantiation of function template specialization 'std::__1::remove_if<boost::void_ptr_iterator<std::__1::__wrap_iter<void 
     **>, Animal>, Sheep *(^)(Animal &)>' requested here 
     std::remove_if(v.begin(), v.end(), 
     ^
test.cpp:12:13: note: candidate function has been explicitly deleted 
    Animal& operator=(const Animal&) = delete; 
      ^
1 error generated. 

Vấn đề là một tính năng của boost::ptr_vector: các vòng lặp không trả lại con trỏ lưu trữ nội bộ. Họ trả lại con trỏ dereferenced.Và do đó khi các container được sử dụng với std::algorithms, các thuật toán cố gắng sao chép các đối tượng được lưu trữ thay vì các con trỏ được lưu trữ cho các đối tượng.

Nếu một trong vô tình quên để làm cho đối tượng đa hình của bạn không copyable, sau đó copy ngữ nghĩa sẽ được tự động cung cấp, dẫn đến một lỗi thời gian chạy thay vì một lỗi thời gian biên dịch:

class Animal 
{ 
public: 
    Animal() = default; 
    virtual ~Animal() = default; 

    virtual void speak() const = 0; 
}; 

Mà bây giờ dẫn đến sai lầm này đầu ra:

Meow 
Baa 
Bark 
Baa 
Meow 
Bark 
Remove all sheep 
destruct Cat 
destruct Dog 
Meow 
Baa 
Bark 
Baa 
destruct Cat 
destruct Sheep 
destruct Dog 
destruct Sheep 

Lỗi thời gian chạy này không thể xảy ra khi sử dụng vector<unique_ptr>.

Sự không phù hợp trở kháng của việc lưu trữ các thùng chứa con trỏ nhưng trình bày các thùng chứa tham chiếu xuất hiện theo tỷ lệ cược với việc sử dụng an toàn các thùng chứa với các thuật toán chung. Thật vậy, đây là lý do tại sao các ptr_containers đi kèm với các phiên bản tùy chỉnh của nhiều thuật toán. Các cách chính xác để làm công việc này với ptr_containers là sử dụng chỉ những thuật toán viên:

v.erase_if([](Animal& p) 
       {return dynamic_cast<Sheep*>(&p);}); 

Nếu bạn cần một thuật toán chuỗi đột biến không được cung cấp như một thành viên của ptr_containers, không bị cám dỗ để đạt cho những trong <algorithm> hoặc các thuật toán chung được cung cấp bởi các bên thứ ba khác.

Tóm lại, tăng :: ptr_containers chứa đầy nhu cầu thực sự khi tùy chọn thực tế duy nhất khác là std::vector<boost::shared_ptr<T>>. Tuy nhiên bây giờ với std::vector<std::unique_ptr<T>>, đối số trên không biến mất. Và dường như có cả lợi thế về tính an toàn và linh hoạt với giải pháp C++ 11. Nếu bạn cần "clone ngữ nghĩa", tôi sẽ nghiêm túc xem xét viết clone_ptr<T> của riêng bạn và sử dụng với các container std và các thuật toán.

Sử dụng lại std :: lib sẽ giữ cho các tùy chọn của bạn chứa mở hơn lib tăng cường (ví dụ: unordered_set/map, forward_list) và sẽ giữ tùy chọn std :: algorithm mở rộng nhất có thể.

Điều đó đang được nói, nếu bạn đã làm việc, mã đã gỡ lỗi đã sử dụng boost :: ptr_containers, không có nhu cầu cấp bách để thay đổi nó.

+0

"đối số trên không còn" - Hôm nay, tôi đã làm một vài thử nghiệm với VS2013-Express và đáng ngạc nhiên là tôi nhận được kết quả hoạt động tốt hơn với 'ptr_vector' so với' vector '(/ O2 release build; boost 1.55). Nhận xét này đi kèm với một YMMV rõ ràng, tôi chưa đào sâu vào điều này, nhưng nếu một người trong một không gian rất chặt chẽ về hiệu suất, nó vẫn có thể thú vị khi nhìn vào cả hai. –

+0

Thú vị, cảm ơn báo cáo. Tôi không có VS2013 để thử nghiệm. Là 'sizeof (unique_ptr ) == sizeof (T *)'? Is/O2 là cài đặt tối ưu hóa cao nhất? –

+0

Vâng, sizeof (uq_ptr) == sizeof (T *) == 4./O2 là "tối đa hóa tốc độ" trong VS. Tôi thấy tốc độ tăng 50% với ptr_vector trên chỉ mục '[]' truy cập. –

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