3

我正在尝试创建和初始化一个包含非平凡类的成员数组的类,该数组包含一些状态和(在某些角落)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,我可以使用标准构造函数初始化程序或使用新的声明内初始化正确初始化它,没有任何问题。无论我尝试什么,对成员数组执行相同操作都会失败并出现上述错误。

我真的不介意数组元素显然是作为临时构造的,然后移动而不是直接构造,但它不编译的事实显然有点令人讨厌。

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

4

2 回答 2

3

这是一个暴露问题的最小示例:

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

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

f0和的两个初始化虽然f1形式相同(都是copy-initialization),f0但是使用list-initialization 直接调用构造函数,而 的初始化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;)这可以避免一些混乱。

编辑:不起作用,从花括号初始化列表复制初始化可能不使用显式构造函数。也就是说,对于

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

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

于 2013-12-16T20:09:56.700 回答
1

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

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

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

于 2021-07-26T08:26:01.183 回答