2015-06-18 2 views
2

У меня есть класс реализованный с использованием Pimpl Ideom:Вы можете поставить Pimpl-класс в векторе

class FooImpl {}; 

class Foo 
{ 
    unique_ptr<FooImpl> myImpl; 
public: 
    Foo(); 
    ~Foo(); 
}; 

А теперь я хочу, чтобы поместить это в станд :: вектор

void Bar() 
{ 
    vector<Foo> testVec; 
    testVec.resize(10); 
} 

Но когда я делаю это, я получаю ошибку компилятора (VC++ 2013)

ошибка C2280: «станд :: unique_ptr> :: unique_ptr (Const станд :: unique_ptr < _Ty, станд :: default_delete < _Ty >> &)»: попытка сослаться проколотой функцию

я получаю ту же ошибку с testVec.emplace_back(); и testVec.push_back(std::move(Foo()));

(В качестве временного решения, используя vector<unique_ptr<Foo>>, кажется, работает, но я не понимаю почему код выше не работает)

Рабочий пример:. http://coliru.stacked-crooked.com/a/b274e1209e47c604

ответ

2

Поскольку std::unique_ptr не является копируемыми, класс Foo не имеет допустимого конструктора копирования.

Вы можете либо deep copy or use a move constructor:

#include <memory> 
#include <vector> 

class FooImpl {}; 

class Foo 
{ 
    std::unique_ptr<FooImpl> myImpl; 
public: 
    Foo(Foo&& f) : myImpl(std::move(f.myImpl)) {} 
    Foo(){} 
    ~Foo(){} 
}; 

int main() { 
    std::vector<Foo> testVec; 
    testVec.resize(10); 
    return 0; 
} 

Живой пример: https://ideone.com/HYtPMu

+0

Но почему размер или emplace_back должны скопировать в первую очередь? – Niki

+1

@nikie, сделайте конструкцию назначения и копирования закрытой, и вы получите сообщение об ошибке создания шаблона, показывающее полный путь их вызова. 'resize' хочет' erase', 'erase' хочет' _Move', '_Move' использует оператор присваивания. –

+0

@ м.с. Кстати, приведенный пример до сих пор не компилируется в VC, а righlty так. Вызов 'resize' требует операции присваивания, а не перемещения конструктора, возможно, различия в VC/gcc/clang. Поскольку op явно использует VC, это не решение. –

2

Так что же происходит, что vector шаблон пытается получить доступ к копии конструктор Foo класса. Вы не предоставили его, поэтому компилятор пытается создать стандартную реализацию, которая вызывает конструктор копирования для всех членов. Поскольку у std::unique_ptr нет конструктора копирования из другого std::unique_ptr (что логично, поскольку он не знает, как скопировать объект), компилятор не может сгенерировать оператор присваивания для Foo, и он терпит неудачу. Так что вы можете сделать, это предоставить конструктор копирования для класса Foo и решить, как обращаться указатель:

#include <memory> 
#include <vector> 

using namespace std; 
class FooImpl {}; 

class Foo 
{ 
    unique_ptr<FooImpl> myImpl; 
public: 
    Foo() 
    { 
    } 
    ~Foo() 
    { 
    } 
    Foo(const Foo& foo) 
    { 
     // What to do with the pointer? 
    } 
    Foo& operator= (const Foo& foo) 
    { 
     if (this != &foo) 
     { 
      // What to do with the pointer? 
     } 
     return *this; 
    } 
}; 

int main(int argc, char** argv) 
{ 
    vector<Foo> testVec; 
    testVec.resize(10); 
    return 0; 
} 
+2

Реализация повторного использования оператора присваивания выглядит плохой идеей, поскольку конструктор копирования вызывается в неинициализированном объекте, поэтому, если вы вызываете оператор присваивания с неинициализированным объектом как аргумент левой стороны, вы получаете неопределенное поведение. Оператор присваивания ожидает инициализированный объект как аргумент левой стороны. –

+0

@SergeRogatch ok это кажется interresting, до C++ 11 Я использовал этот шаблон, вы можете немного подробнее рассказать? Я понимаю логику, но поскольку я только назначаю переменную-член, не так ли? Я имею в виду, не копирует ли колл все члены-участники раньше? Можете ли вы дать мне образец неопределенного поведения? Мне было бы интересно понять последствия. –

+0

Вы правы, конструктор копирования будет вызывать конструкторы-члены переменных-членов перед вводом {если они не инициализируются пользователем в списке инициализации конструктора копирования: http://stackoverflow.com/a/754754/1915854. Но я думаю, что необработанные указатели (например, void *) и переменные примитивных типов данных (например, int) будут содержать мусор. Это разрешено в конструкторе копирования, потому что он вызывается неинициализированным объектом. Но оператор присваивания ожидает, что инициализированный объект будет левым. Оператор присваивания может удалить необработанный указатель (void *), и это может привести к повреждению памяти для неинициализированного –

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