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

std::unique_ptr deleted function, initializer_list - driven allocation

本文关键字:list initializer 分配 unique ptr 删除 std 函数      更新时间:2023-10-16

全部,

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

$uname-

Linux。。3.5.0-21-generic#32 Ubuntu SMP 12月11日星期二18:51:59 UTC 2012 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

根据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() {..}

也许有答案,但我发现在Main()中对wf[]使用c++11风格的迭代会导致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风格的迭代正在进行对象复制。

根据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
}

产生删除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()) {}
    // ...

最好的方法是使类可移动,而实现这一点的一个好方法是遵循为零的规则