2012-02-27 6 views
15

С C++ 11, я спрашивал себя, есть ли замена boost :: ptr_containers в C++ 11. Я знаю, что могу использовать, например. a std::vector<std::unique_ptr<T> >, но я не уверен, что это полная замена. Каков рекомендуемый способ обращения с этими случаями?stl container with std :: unique_ptr's vs boost :: ptr_container

+2

При использовании 'unique_ptr' вам все равно придется разыменовывать узлы, но кроме этого они должны вести себя одинаково. –

ответ

19

Они действительно решают две похожие, но разные проблемы.

Контейнер-указатель - это способ хранения объектов в контейнере, которые просто являются указателями на выделенную память, а не на значения. Они делают все, что в их силах, до скрыть фактом, что они являются контейнером указателей. Это означает:

  • Записи в контейнере не могут быть NULL.
  • Значения, которые вы получаете от итераторов и функций: ссылки к типу, а не указатели на тип.
  • Работа со многими стандартными алгоритмами может быть ... сложной. И «сложно», я имею в виду сломанный. Контейнеры-указатели имеют свои встроенные алгоритмы.

Однако тот факт, что указатель контейнеры знать что они контейнеры указателей, они могут предложить некоторые новые функциональные возможности:

  • Функция clone элемента, который выполняет глубокую копию, с помощью использования определенной «клонируемой» концепции о типе объекта.
  • Способность контейнера освобождать собственность на свои объекты (например, после мелкой копии).
  • Встроенные функции для передачи права собственности на другие контейнеры.

Они действительно совсем другие концепции. Существует много вещей, которые вам придется делать вручную, чтобы контейнеры-указатели могли автоматически выполнять специализированные функции.

Если вам действительно нужен контейнер указателей, тогда вы можете использовать контейнеры unique_ptr. Но если вам нужно сохранить кучу объектов, которые вы выбрали для выделения кучи, и вы хотите играть с ними специальные игры с участием владельца и т. Д., То контейнеры-указатели - не плохая идея.

+1

Я думаю, вы могли бы сказать, что один из них явно является контейнером указателей, тогда как другой является контейнером, предназначенным для полиморфных объектов ... – Mehrdad

22

Я решил написать короткую программу, в которую были помещены несколько полиморфных объектов в контейнер (указателем на кучу), а затем использовать этот контейнер с помощью std :: algorithm. В качестве примера я выбрал std::remove_if.

Вот как я хотел бы сделать это с 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(); 
} 

Это выходы:

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 

, который выглядит хорошо для меня. Тем не менее я нашел перевод это ptr_vector проблематичной:

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. 

Проблема заключается особенность boost::ptr_vector: итераторы не возвращают внутренне сохраненные указатели. Они возвращают указатели разыгрываются.И поэтому, когда контейнер используется с std::algorithms, алгоритмы пытаются скопировать сохраненные объекты вместо сохраненных указателей на объекты.

Если один случайно забывает сделать свои полиморфные объекты, не копируемыми, а затем скопировать Семантика автоматически подается, в результате чего время выполнения ошибки вместо компиляции ошибки времени:

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

    virtual void speak() const = 0; 
}; 

который теперь приводит это ошибочное выход:

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 

Эта ошибка времени выполнения не может произойти при использовании vector<unique_ptr>.

Несоответствие импеданса хранения контейнеров указателей, но представляющих контейнеры с ссылками, несовместимо с безопасным использованием контейнеров с использованием общих алгоритмов. В самом деле, вот почему ptr_containers поставляются с пользовательскими версиями многих алгоритмов. Правильный способ сделать эту работу с ptr_containers является использование только эти алгоритмы члена:

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

Если вам нужен меняющийся алгоритм последовательности не входит в комплект в качестве члена ptr_containers, не поддавайтесь искушению, чтобы достигнуть тех, в <algorithm>, или эти общие алгоритмы, предоставляемые другими третьими сторонами.

Таким образом, boost :: ptr_containers заполнили настоящую потребность, когда единственным практичным вариантом было std::vector<boost::shared_ptr<T>>. Однако теперь с std::vector<std::unique_ptr<T>>, служебные аргументы исчезли. И, по-видимому, с решением C++ 11 существуют преимущества безопасности и гибкости. Если вам нужна «семантика клонов», я бы серьезно подумал о написании собственного clone_ptr<T> и использовании этого с помощью контейнеров и алгоритмов std.

Повторное использование std :: lib будет держать ваши варианты контейнеров более открытыми, чем boost lib (например, unordered_set/map, forward_list), и это будет поддерживать как можно более широкие возможности std :: algorithmms.

При этом, если у вас есть рабочий, отлаженный код уже использует boost :: ptr_containers, нет необходимости срочно его менять.

+0

«служебный аргумент ушел» - сегодня я провел несколько тестов с VS2013-Express и удивительно я получаю стабильно лучшие результаты производительности с помощью 'ptr_vector', чем с' vector '(/ release O2, boost 1.55). Этот комментарий приходит с явным YMMV, я еще не вникнул в это, но если у вас очень плотное пространство, то может быть интересно посмотреть на оба. –

+0

Интересно, спасибо за отчет. У меня нет VS2013 для экспериментов. Is 'sizeof (unique_ptr ) == sizeof (T *)'? Is/O2 - самая высокая настройка оптимизации? –

+0

Да, sizeof (uq_ptr) == sizeof (T *) == 4./O2 «максимизируется для скорости» в VS. Я вижу 50% -ное увеличение скорости с помощью ptr_vector по индексу '[]' access. –