成员数组对象的初始化避免移动构造函数

时间:2013-12-16 17:53:02

标签: gcc c++11 initialization move member-variables

我正在尝试创建并初始化一个包含非平凡类的成员数组的类,该类包含一些状态和(在某些角落)std::atomic_flag。从C ++ 11开始,应该能够初始化成员数组。

代码(剥离到最小)看起来像这样:

class spinlock
{
    std::atomic_flag flag;
    bool try_lock() { return !flag.test_and_set(std::memory_order_acquire); }
public:
    spinlock() : flag(ATOMIC_FLAG_INIT){};
    void lock()     { while(!try_lock()) ; }
    void unlock()   { flag.clear(std::memory_order_release); }
};

class foo
{
    spinlock lock;
    unsigned int state;
public:
    foo(unsigned int in) : state(in) {}
};

class bar
{
    foo x[4] = {1,2,3,4}; // want each foo to have different state
public:
    //...
};

如果我理解编译器输出正确,这似乎来构造成员数组,但是构造临时对象并调用移动/复制构造函数,随后调用子类中的移动构造函数,以及恰好在std::atomic_flag中删除了一个。我得到的编译器输出(gcc 4.8.1)是:

[...] error: use of deleted function 'foo::foo(foo&&)'
note: 'foo::foo(foo&&)' is implicitly deleted because the default definition would be ill-formed
error: use of deleted function 'spinlock::spinlock(spinlock&&)'
note: 'spinlock::spinlock(spinlock&&)' is implicitly deleted because [...]
error: use of deleted function 'std::atomic_flag::atomic_flag(const std::atomic_flag&)'
In file included from [...]/i686-w64-mingw32/4.8.1/include/c++/atomic:41:0
[etc]

如果我删除数组而只是在foo中放置一个bar成员,我可以使用标准构造函数初始化程序正确初始化它,或使用新的声明初始化,没有任何问题。无论我尝试什么,对成员数组执行相同操作都会失败并出现上述错误。

我真的不介意数组元素显然是构造为临时数据然后移动而不是直接构造,但是它不编译的事实显然有些显而易见。

我是否有办法强制编译器构造(不移动)数组元素,或者我可以解决这个问题?

2 个答案:

答案 0 :(得分:2)

这是揭露问题的最小例子:

struct noncopyable
{
    noncopyable(int) {};
    noncopyable(noncopyable const&) = delete;
};

int main()
{
    noncopyable f0 = {1};
    noncopyable f1 = 1;
}

虽然f0f1的两个初始化具有相同的格式(都是复制初始化),但f0使用直接调用的列表初始化一个构造函数,而f1的初始化基本上等同于foo f1 = foo(1);(创建一个临时文件并将其复制到f1)。

这种微小的差异也体现在数组的情况下:

noncopyable f0[] = {{1}, {2}, {3}, {4}};
noncopyable f1[] = {1, 2, 3, 4};

聚合初始化定义为成员的复制初始化[dcl.init.aggr] / 2

  

每个成员都是从相应的 initializer-clause 中复制初始化的。

因此,f1基本上表示f1[0] = 1, f1[1] = 2, ..(此表示法将描述数组元素的初始化),其具有与上述相同的问题。 OTOH,f0[0] = {1}(作为初始化)再次使用列表初始化,它直接调用构造函数,而不(在语义上)创建临时。


你可以使你的转换构造函数explicit;)这可以避免一些混淆。

编辑:无法工作,从braced-init-list复制初始化可能不会使用显式构造函数。也就是说,对于

struct expl
{
    explicit expl(int) {};
};

初始化expl e = {1};格式不正确。出于同样的原因,expl e[] = expl e {1};;是不正确的。 {{1}}仍然是格式良好的。

答案 1 :(得分:0)

Gcc 拒绝编译具有虚拟析构函数的对象数组的列表初始化,直到版本 10.2。在 10.3 中已修复。 例如。如果来自@dyp 的 noncopyable 答案有一个虚拟析构函数,gcc 无法编译行:

noncopyable f0[] = {{1}, {2}, {3}, {4}}; 

争论删除副本并移动 c-rs。但在 10.3 及更高版本下成功编译。