6

全部,

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

$ unname -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++ --版本

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<> 的表现力与裸 ptr 相比似乎受到限制。

我的问题是:

  • 飞行员失误?
  • 编译器错误?
  • 我可以让我想要的代码在一些改变的情况下工作吗?

谢谢你。

编辑 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

根据乔纳森的回复,以下代码不会抑制隐式移动 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 风格的迭代正在做一个对象复制是不言而喻的。

4

2 回答 2

8

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

当聚合由初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。每个成员都是从相应的初始化子句复制初始化的。[...]

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

但是,您的类包含一个成员,其类型是 的实例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
}
于 2013-02-22T20:29:43.767 回答
4

产生删除 fcxn 的幕后机制;

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

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

unique_ptr正在将您从严重的错误中解救出来。使用裸指针,您的类型非常不安全并且会导致未定义的行为,因为您没有复制构造函数,因此指针被复制然后被两个不同的对象删除两次。繁荣,你的程序有未定义的行为。 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()) {}
    // ...

最好的方法是让类可移动,一个好的方法是遵循零规则

于 2013-02-22T22:39:30.340 回答