C++中使用圆括号调用拷贝构造函数为何触发编译错误
问题现象
你实现了一个包含自定义拷贝构造、移动构造的类A,代码如下:
#include <cstring> #include <utility> class A { private: int *pa_; std::size_t size_; public: explicit A(std::size_t size) : pa_{new int[size]}, size_{size} {}; // copy constructor A(const A &other) : pa_{new int[other.size_]}, size_{other.size_} { std::memcpy(pa_, other.pa_, size_); } // move constructor A(A &&other) : pa_{other.pa_}, size_{other.size_} { other.pa_ = nullptr; } };
编写测试代码验证构造函数调用行为时,发现独立语句中使用圆括号调用拷贝构造会触发编译错误,测试代码如下:
int main(int argc, char const *argv[]) { A a1{100}; // ok A(a1); // error A a2 = A(a1); // ok A{a1}; // ok A aa[] = { // ok A(a1), A(a1)}; A(std::move(a1)); // ok }
使用g++-11 -std=c++17 -Wall -pedantic-errors编译,输出错误信息如下:
example.cpp: In function 'int main(int, const char**)': example.cpp:30:4: warning: unnecessary parentheses in declaration of 'a1' [-Wparentheses] 30 | A(a1); // error | ^~~~ example.cpp:30:4: note: remove parentheses 30 | A(a1); // error | ^~~~ | - - example.cpp:30:5: error: redeclaration of 'A a1' 30 | A(a1); // error | ^~ example.cpp:29:5: note: 'A a1' previously declared here 29 | A a1{100}; // ok | ^~ example.cpp:33:5: warning: unused variable 'aa' [-Wunused-variable] 33 | A aa[] = { // ok | ^~
根本原因
这个反直觉的行为来自C++标准规定的歧义解析规则,也就是社区常说的最令人头疼的解析(Most Vexing Parse):
- 当一段代码同时符合「变量声明」和「创建临时对象的表达式语句」两种语法规则时,编译器必须优先将其解析为变量声明。
- 你写的
A(a1);不会被识别为「调用拷贝构造生成A类型临时对象」,而是会被解析为声明一个名为a1的A类型变量,包裹a1的圆括号在声明语法中是冗余合法的,整句等价于A a1;。 - 上一行代码已经声明过同名变量
A a1{100};,同一作用域下重复声明同名变量自然触发重定义错误,编译器输出的「a1声明存在不必要括号」的警告也完全对应这个解析逻辑。
其余写法能正常工作的原因也很明确:
A a2 = A(a1);中A(a1)位于初始化表达式的右值位置,不存在被解析为独立声明的歧义,会被正确识别为临时对象构造。A{a1};使用花括号列表初始化语法,C++标准明确规定该形式不会触发上述歧义解析,会直接识别为临时对象构造。- 数组初始化列表内的
A(a1)、独立语句中的A(std::move(a1)),括号内分别是初始化子句、带作用域限定的函数调用表达式,不可能被解析为变量名,因此不存在歧义,会正常触发拷贝/移动构造。
解决方案
如果确实需要在独立语句中使用圆括号形式构造临时对象,可以通过以下写法消除歧义:
- 在外层额外包裹一层括号,让语句不符合声明语法规则:
(A(a1)); // 无法被解析为变量声明,会正确调用拷贝构造生成临时对象 - 直接使用花括号初始化形式,从语法根源避免歧义:
A{a1}; // 无歧义构造临时对象
日常开发中,C++11及以后版本推荐优先使用花括号初始化,可以规避绝大多数这类解析歧义问题。
内容的提问来源于stack exchange,提问作者ph3
相关产品推荐
相关产品推荐

