2015-06-18 11 views
2

我必須使用PIMPL Ideom實現類:你可以把一個PIMPL級矢量內

class FooImpl {}; 

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

現在我想把這變成一個std :: vector的

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

但是,當我這樣做,我得到一個編譯錯誤(VC++ 2013)

錯誤C2280:「的std ::的unique_ptr> ::的unique_ptr(常量的std ::的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是不可拷貝,類沒有有效的拷貝構造函數。

你既可以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'想'擦除','擦除'想'_Move','_Move'使用賦值運算符。 –

+0

@ m.s。順便說一句,提供的例子仍然不能在VC中編譯,並且很重要。 'resize'調用需要一個賦值操作,而不是移動構造函數,可能是VC/gcc/clang的區別。由於該操作明確使用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好吧,這似乎interresting,之前C++ 11我一直在使用這種模式,你能詳細說一點嗎?我理解邏輯,但據我只分配成員變量是不是很好?我的意思是,複製委員會之前並沒有召集所有會員。你能給我一個未定義的行爲樣本嗎?我有興趣瞭解後果。 –

+0

你是對的,複製構造函數會在輸入前調用默認的成員變量構造函數{如果它們沒有被複制構造函數初始化列表中的用戶初始化:http://stackoverflow.com/a/754754/1915854。但是我認爲原始指針(例如void *)和原始數據類型的變量(如int)將包含垃圾。這是在複製構造函數中允許的,因爲它在未初始化的對象上被調用。但賦值運算符期望初始化對象爲左側。賦值運算符可能會刪除一個原始指針(void *),並且這會破壞未初始化的內存 –

相關問題