我在理解何时以及是否调用 move 构造函数或 move 赋值运算符时遇到一些问题,特别是在具有常量数据成员的类的上下文中。 考虑类
template<typename T> class A {
const*T const P ; // constant data member
explicit A(const*T p) : P(p) { std::cerr<<" ctor: P="<<P<<'\n'; }
void test() const { std::cerr" test: P="<<P<<'\n'; }
// move and copy constructors and assignment operators here
};
和测试程序
class B {
int X[100];
A<B> get_a() const { return A<B>(this); }
};
int main() {
B b;
A<B> a = b.get_a(); // which operator/ctor is used for '=' here?
a.test();
}
那么编译的结果是不同的,这取决于为类 A<>
中的 move 构造函数和 move 赋值运算符提供的定义。 ,还有编译器。
1 类中没有任何进一步的声明 A<>
(如上所述),g++ (4.7.0) 和 icpc (13.0.1) 都可以正常编译(使用选项 -std=c++11
)并产生预期的输出
ctor: P=0x7fffffffd480
test: P=0x7fffffffd480
2如果我声明
A&A::operator=(A&&) = delete;
A&A::operator=(const A&) = delete;
(鉴于必须初始化列表初始化的常量数据成员,这似乎是明智的),但不提供任何进一步的构造函数,g++ 编译失败但 icpc 没问题。如果另外我定义了一个(或两个)
A::A(A&&) = default;
A::A(const A&) = default;
两个编译器都很高兴。但是,g++ 对这种组合并不满意
A::A(A&&) = delete;
A::A(const A&) = default;
虽然 icpc 很高兴。
3 如果我玩与2相同的游戏,除了A::A(A&&) = default;
被替换为
A::A(A&&a) : P(a.P) { std::cerr<<" move ctor: P="<<P<<'\n'; } // never called?
(和 A::A(const A&)
的等价物),结果完全相同,特别是没有输出是从这些显式 move 和复制 ctors 生成的。
那么=
用的是哪个运算符在 main()
? (为什么上次测试没有输出?)
考虑到A<>
,为什么这里允许这个操作呢?有一个常量数据成员(如果我将成员 const*T const P;
替换为 const T&R
,结果是相同的)?
最后,对于 g++ 和 icpc 的不同行为,如果有的话,哪个是正确的?
最佳答案
A<B> a = b.get_a();
不是赋值,而是从右值初始化 a
。此语法在 C++0x 下应该失败,如果
- move 构造函数被删除,
- move 构造函数被声明为
显式
, - 复制构造函数被删除,并且没有定义 move 构造函数,
- 没有定义 move 构造函数,同时定义或删除了 move 赋值。
复制赋值运算符的声明或删除不应有任何影响。
更正:与复制构造函数(即使提供了用户定义的复制赋值运算符也会合成)不同,如果定义了用户定义的 move 赋值,编译器不会合成 move 构造函数。因此,上面的列表应该修改 4(我现在已经这样做了)。
因此,在我看来,
- 在问题 [1] 中,两个编译器都正确运行,
- 在 [2]/1 中,gcc 行为正确( move 赋值阻止生成 move 构造函数),icpc 错误,
- 在[2]/2中,两个编译器都是正确的,
- 在 [2]/3 中,gcc 是正确的,因为 move 构造函数被显式删除; icpc错了,
- [3] 对我来说是个谜。你确定你是对的吗?
关于c++ - move 具有常量数据成员或引用成员的类的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13565594/