我在理解何时以及是否调用移动构造函数或移动赋值运算符时遇到了一些问题,尤其是在具有常量数据成员的类的上下文中。考虑类
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<>
中的移动构造函数和移动赋值运算符提供的定义以及编译器。
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;
(考虑到必须初始化的常量数据成员列表,这似乎是合理的),但不提供任何进一步的ctor,编译失败,使用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&)
),结果完全相同,特别是没有从这些显式移动和复制因子生成输出。
那么main()
中的=
使用了哪个运算符呢?(为什么在上次测试中没有输出?)
既然A<>
有一个恒定的数据成员(如果我用const T&R
替换成员const*T const P;
,结果是相同的),为什么这里允许这种操作?
最后,在g++和icpc的不同行为的情况下,如果有的话,哪一个是正确的?
A<B> a = b.get_a();
不是赋值,而是从右值初始化a
。如果
- 移动构造函数被删除
- 移动构造函数被声明为CCD_ 12
- 复制构造函数被删除并且没有定义移动构造函数
- 没有定义move构造函数,同时定义或删除了move赋值
声明或删除副本分配运算符不应产生任何影响。
更正:与复制构造函数(即使提供了用户定义的复制赋值运算符也会合成)不同,如果定义了用户定义移动赋值,编译器不会合成移动构造函数。因此,上述清单应修改为4(我现在已经这样做了)。
因此,在我看来,
- 在问题[1]中,两个编译器的行为都是正确的
- 在[2]/1中,gcc行为正确(move赋值阻止生成move构造函数),icpc错误
- 在[2]/2中,两个编译器都是正确的
- 在[2]/3中,gcc是正确的,因为move构造函数被显式删除;icpc错了
- [3] 对我来说是个谜。你确定你是对的吗