std :: unique_ptr删除了函数,initializer_list - 驱动分配

时间:2013-02-22 20:17:03

标签: c++ c++11 unique-ptr initializer-list

全部,

当我使用初始化列表格式实例化窗口小部件数组时,指向成员变量窗口小部件实例的裸指针编译但在更改为std :: unique_ptr<>之后gcc给出了有关已删除函数的编译错误。

$ uname -a

Linux .. 3.5.0-21-generic#32-Ubuntu SMP Tue Dec 11 18:51:59 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux

$ g ++ --version

g ++(Ubuntu / Linaro 4.7.2-5ubuntu1)4.7.2

此代码提供以下编译器错误:

#include <stdlib.h>
#include <memory>

class Widget
{
public:
    Widget() {}
};

class W1 : public Widget
{
public:
    W1() {}
};

class W2 : public Widget
{
public:
    W2() {}
};

class WFactory
{
public:
    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}

    ~WFactory() { _w.reset(nullptr); }
    // ~WFactory() { delete _w; }  <--- for naked ptr

private:
    // NOTE: does not compile
    std::unique_ptr<Widget>  _w; 
    // NOTE: does compile
    // Widget* _w;
};

int main()
{
    std::unique_ptr<Widget> a(new W1()); // <--- compiles fine

    WFactory wf[] { 4, "msg" };          // <--- compiler error using unique_ptr<>
}

错误:

$ g++ -o unique_ptr  -std=c++11 -Wall  unique_ptr.cpp 
unique_ptr.cpp: In function ‘int main()’:
unique_ptr.cpp:36:30: error: use of deleted function ‘WFactory::WFactory(const WFactory&)’
unique_ptr.cpp:22:7: note: ‘WFactory::WFactory(const WFactory&)’ is implicitly deleted because the default definition would be ill-formed:
unique_ptr.cpp:22:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Widget; _Dp = std::default_delete<Widget>; std::unique_ptr<_Tp, _Dp> = std::unique_ptr<Widget>]’
In file included from /usr/include/c++/4.7/memory:86:0,
             from unique_ptr.cpp:2:
/usr/include/c++/4.7/bits/unique_ptr.h:262:7: error: declared here
unique_ptr.cpp:36:30: error: use of deleted function ‘WFactory::WFactory(const WFactory&)’
unique_ptr.cpp:36:14: warning: unused variable ‘wf’ [-Wunused-variable]

我对两种情况都感到茫然:幕后产生删除fcxn的机制;或者更简单地说,为什么std :: unique_ptr&lt;&gt;的表现力?与裸体相比,似乎受到限制。

我的问题是:

  • 飞行员错误?
  • 编译错误?
  • 我可以通过一些更改来获取我想要的代码吗?

谢谢。

修改1

根据您的回答,我感谢,我可以对WFactory进行以下更改:

标记为不道德行为代码

class WFactory
{
public:
    WFactory(const WFactory& wf)
    {
        (const_cast<WFactory&>(wf)).moveto(_w);
    }

    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}

    ~WFactory() { _w.reset(nullptr); }

    void moveto(std::unique_ptr<Widget>& w)
    {
        w = std::move(_w);
    }
private:
    std::unique_ptr<Widget>  _w; 
};

现在程序编译并运行。我很欣赏标准人员出于某种原因编写了规范,因此我将我的结果作为一个善意的专业化发布在我的案例中,我真的想强调ptr的独特性。

修改2

根据Jonathan的回复,以下代码不会抑制隐式移动ctor:

class WFactory
{
public:
    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}

private:
    std::unique_ptr<Widget>  _w; 
};

请注意,根本没有~WFactory() {..}

也许有ya-ans,但我发现在Main()中使用wf []的c ++ 11风格的迭代会带来no-copy-ctor-for-WFactory错误。那就是:

int Main()
..
    WFactory wf[] { 4, "msg" };

    for ( WFactory iwf : wf )    <---- compiler error again
        // ..

    for (unsigned i = 0; i < 2; ++i)  <--- gcc happy
        wf[i] //  ..
}

我想新的c ++ 11风格的迭代正在进行对象复制,这是不言而喻的。

2 个答案:

答案 0 :(得分:8)

根据C ++ 11标准的第8.5.1 / 2段:

  

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。每个成员都从相应的initializer子句中复制初始化。 [...]

对于每个元素,然后,复制初始化涉及创建目标类型的临时值,然后用于复制构造数组的元素。

但是,您的类包含一个类型为unique_ptr的实例的成员,该成员是不可复制的。这使得你的课程也不可复制。

此外,虽然unique_ptr可移动的,但是您的类不是,因为编译器隐式生成移动构造函数会因显式定义的析构函数的存在而被抑制。如果不是这种情况(即,如果您为类明确定义了移动构造函数),则复制初始化将起作用(见8.5 / 15)。

尝试更改WFactory的定义,如下所示:

class WFactory
{
public:
    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}
    WFactory(WFactory&& f) : _w(std::move(f._w)) {}
    ~WFactory() { _w.reset(nullptr); }
private:
    std::unique_ptr<Widget> _w;
};

int main()
{
    std::unique_ptr<Widget> a(new W1());
    WFactory wf[] { 4, "msg" };          // OK
}

答案 1 :(得分:4)

  

幕后产生删除fcxn的机制;

如果类型是可复制的或可移动的,并且unique_ptr不可复制,则只能初始化数组,因此默认情况下,具有unique_ptr成员的类不可复制,并且您的类型具有用户定义的析构函数,它禁止隐式移动构造函数,因此您的类型也不可移动。

  

或更简单地说,为什么std::unique_ptr<>的表现力与裸体ptr相比似乎受到限制。

unique_ptr让您免于严重的错误。使用裸指针,您的类型非常不安全并将导致未定义的行为,因为您没有复制构造函数,因此指针被复制,然后被两个不同的对象删除两次。 Boom,你的程序有不确定的行为。 unique_ptr通过阻止您的类被复制来修复您的类,这是安全和正确的。

您可以通过多种方式使其工作,最简单的方法是删除用户定义的析构函数,这会使您的类可以移动,并且数组初始化将被编译。

或者,如果由于其他原因需要用户定义的析构函数,您仍然可以通过编写用户定义的移动构造函数来显式移动_w成员来使其工作。

如果由于某种原因无法实现,可以通过添加默认构造函数使其工作,因此可以默认构造数组元素,然后移动分配给它们:

class WFactory
{
public:
    WFactory() = default;
    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}

private:
    std::unique_ptr<Widget>  _w; 
};

int main()
{  
    WFactory wf[2];
    wf[0] = WFactory(4);
    wf[1] = WFactory("msg");
}

你编辑的版本是不道德的,非常可疑,你不应该抛弃const这样的,你不应该从左值,特别是const左值。不要去那儿。而是更改您使用类的方式以避免需要复制它,编写一个有效的复制构造函数来执行所拥有对象的深层副本:

class Widget
{
public:
    Widget() {}
    virtual std::unique_ptr<Widget> clone() const = 0;
};

class W1 : public Widget
{
public:
    W1() {}
    virtual std::unique_ptr<Widget> clone() const
    { return std::unique_ptr<Widget>(new W1(*this)); }
};

class W2 : public Widget
{
public:
    W2() {}
    virtual std::unique_ptr<Widget> clone() const
    { return std::unique_ptr<Widget>(new W2(*this)); }
};

class WFactory
{
public:
    WFactory(const int i)   : _w(new W1()) {}
    WFactory(const char* s) : _w(new W2()) {}
    WFactory(const WFactory& w) : _w(w._w->clone()) {}
    // ...

最好的方法是使课程可以移动,这样做的好方法是遵循rule of zero