【问题标题】:Can you put a pimpl-Class inside a vector你能把一个 pimpl-Class 放在一个向量中吗
【发布时间】:2015-09-03 17:36:57
【问题描述】:

我有一个使用 PImpl Ideom 实现的类:

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(const std::unique_ptr<_ty>> &)' : 试图引用已删除的函数

testVec.emplace_back();testVec.push_back(std::move(Foo())); 出现同样的错误

(作为一种解决方法,使用vector&lt;unique_ptr&lt;Foo&gt;&gt; 似乎可行,但我不明白为什么上面的代码不起作用。)

工作示例:http://coliru.stacked-crooked.com/a/b274e1209e47c604

【问题讨论】:

    标签: c++ class c++11 smart-pointers


    【解决方案1】:

    由于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

    【讨论】:

    • 但是为什么 resize 或 emplace_back 必须首先复制呢?
    • @nikie,将赋值和复制构造函数设为私有,您将收到一个模板实例化错误,显示它们如何被调用的完整路径。 resize 想要eraseerase 想要_Move,'_Move` 使用赋值运算符。
    • @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;
    }
    

    【讨论】:

    • 实现复制构造函数重用赋值运算符似乎是一个坏主意,因为复制构造函数是在未初始化的对象上调用的,所以如果你用未初始化的对象作为左侧参数调用赋值运算符,你会得到 undefined行为。赋值运算符期望初始化对象作为左侧参数。
    • @SergeRogatch 好的,这似乎很有趣,在 C++11 之前我一直在使用这种模式,你能详细说明一下吗?我理解逻辑,但就我只分配成员变量来说不是很好吗?我的意思是复制 ctor 之前不是也调用了所有成员 ctor 吗?你能给我一个未定义行为的样本吗?我有兴趣了解后果。
    • 你是对的,复制构造函数会在进入{之前调用成员变量的默认构造函数,如果它们没有被用户在复制构造函数初始化列表中初始化:stackoverflow.com/a/754754/1915854。但我认为原始指针(例如 void*) 和原始数据类型(例如 int)的变量将包含垃圾。这在复制构造函数中是允许的,因为它是在未初始化的对象上调用的。但是赋值运算符期望初始化的对象在左侧。赋值运算符可能会删除原始指针 (void*),这会损坏未初始化的内存
    • @SergeRogatch 好的,我明白了你的意思,我在想还有更可怕的东西。至于原始指针成员——这取决于用户来做一个正确的初始化列表并将它们归空并检查赋值运算符中的值,这至少是我会做的。感谢您对您的陈述的解释:)但我仍然会坚持我的信念,如果代码编写正确,那就没问题了。另一件事是在初始化列表和赋值运算符中可能重叠的重复操作,但这是另一回事。
    猜你喜欢
    • 2011-04-11
    • 1970-01-01
    • 2018-01-13
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 2021-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多