2012-01-06 2 views
5

Контейнер unique_ptr, по-видимому, имеет мало смысла: вы не можете использовать его с списками инициализаторов, и мне не удалось выполнить итерацию через контейнер (обходные пути ниже). Я что-то не понимаю? Или когда имеет смысл использовать unique_ptr и STL-контейнеры?Когда имеет смысл использовать unique_ptr с контейнерами STL? (C++ 11)

#include <memory> 
#include <vector> 

using namespace std; 

struct Base { void go() { } virtual ~Base() { } }; 
// virtual ~Base() = default; gives 
// "declared virtual cannot be defaulted in the class body" why? 

class Derived : public Base { }; 

int main() { 

    //vector<unique_ptr<Base>> v1 = { new Derived, new Derived, new Derived }; 
    //vector<shared_ptr<Base>> v2 = { new Derived, new Derived, new Derived }; 
    vector<Base*> v3 = { new Derived, new Derived, new Derived }; 
    vector<shared_ptr<Base>> v4(v3.begin(), v3.end()); 
    vector<unique_ptr<Base>> v5(v3.begin(), v3.end()); 

    for (auto i : v5) { // works with v4 
    i->go(); 
    } 
    return 0; 
} 


Следующие вопросы помогли мне найти эти обходные пути

ответ

14
for (auto i : v5) { 
    i->go(); 
} 

Должно быть

for (auto& i : v5) { // note 'auto&' 
    i->go(); 
} 

Иначе вы будете пытаться скопировать текущий элемент.

Кроме того, вы не можете использовать список инициализаций, подобный этому, поскольку конструкторы std::unique_ptr и std::shared_ptr обозначены explicit. Вам нужно сделать что-то вроде этого:

#include <iterator> // make_move_iterator, begin, end 

template<class T> 
std::unique_ptr<T> make_unique(){ // naive implementation 
    return std::unique_ptr<T>(new T()); 
} 

std::unique_ptr<Base> v1_init_arr[] = { 
    make_unique<Derived>(), make_unique<Derived>(), make_unique<Derived>() 
}; 

// these two are only for clarity 
auto first = std::make_move_iterator(std::begin(v1_init_arr)); 
auto last = std::make_move_iterator(std::end(v1_init_arr)); 
std::vector<std::unique_ptr<Base>> v1(first, last); 

std::vector<std::shared_ptr<Base>> v2 = { 
    std::make_shared<Derived>(), 
    std::make_shared<Derived>(), 
    std::make_shared<Derived>() 
}; 

И это хорошая вещь ™, потому что в противном случае вы могли бы утечку памяти (если один из более поздних конструкторов бросков, бывшие те, которые не привязаны к смарт-указателям). Необходима подсказка для unique_ptr, поскольку списки инициализаторов копируют свои аргументы, и поскольку unique_ptr s не могут быть скопированы, у вас возникнет проблема.


Тем не менее, я использую std::map<std::string, std::unique_ptr<LoaderBase>> для словаря погрузчиков в одном из моих проектов.

+1

'вектор > v1 = {make_unique (), make_unique (), make_unique ()};' <- Вы проверить это? –

+0

Как указывает @Benjamin, списки инициализаторов делают копии по определению, поэтому их нельзя использовать с объектами только для перемещения. – ildjarn

+0

@Benjamin: По общему признанию, нет, поскольку в настоящее время у меня нет компилятора, который поддерживает списки инициализаций, подобные этому.Я не понимаю, какая проблема может возникнуть? – Xeo

1

unique_ptr имеет смысл в контейнерах STL, когда контейнер хранит объекты, которые невозможно скопировать. Или если это дорого или просто неправильно скопировать их.

Вы получаете такую ​​же функциональность как ваш

vector<Base*> v3 = { new Derived, new Derived, new Derived }; 

Но без утечек памяти, что v3 приглашает.

+1

Или когда вам нужно полиморфное поведение. – ildjarn

+0

Вы можете поместить непокрываемые объекты (такие как 'unique_ptr') в контейнеры C++ 11; хотя некоторые контейнеры требуют, чтобы они были подвижными. –

1

Фактически вы можете выполнять итерацию контейнера без проблем с помощью std::unique_ptr<T> ... вам просто нужно получить доступ к ссылке (то есть не к копии) уникального указателя или вам действительно нужно использовать тип итератора с помощью контейнер. В вашем случае это будет что-то вроде vector<unique_ptr<Base>>::iterator или vector<unique_ptr<Base>>::const_iterator.

Смежные вопросы