关于动态数组初始化时额外移动构造函数调用的疑问
动态数组初始化时额外移动构造调用的原因分析
问题描述
我编写了包含unique_ptr成员的MyInt类,通过CMake添加-fno-elide-constructors参数禁用了复制消除。在执行new MyInt[2]{11, 22}动态初始化数组时,程序输出显示存在额外的移动构造函数调用,已知临时对象会被移动到数组对应内存位置,但无法理解这些额外调用的原因及作用,特此求助。
代码实现
#include <iostream> #include <memory> using namespace std; class MyInt { unique_ptr<int> ptr; public: MyInt(int val) { cout << __PRETTY_FUNCTION__ << "this: <" << this << ">, val: <" << val << ">" << endl; ptr = make_unique<int>(val); } ~MyInt() { cout << __PRETTY_FUNCTION__ << "this: <" << this << ">" << endl; } MyInt(MyInt&& other) noexcept: ptr(move(other.ptr)) { cout << __PRETTY_FUNCTION__ << "this: <" << this << ">, other :<" << &other << ">" << endl; } MyInt& operator=(MyInt&& other) noexcept { cout << __PRETTY_FUNCTION__ << "this: <" << this << ">, other :<" << &other << ">" << endl; if (this != &other) { ptr = move(other.ptr); other.ptr = nullptr; } return *this; } }; int main(int argc, char *argv[]) { MyInt *aptr = new MyInt[2]{11, 22}; for (size_t i = 0; i < 2; ++i) { cout << "aptr[" << i << "] :" << &aptr[i] << endl; } cout << "deleting :" << endl; delete [] aptr; cout << "exiting" << endl; return 0; }
程序输出
onkar@DESKTOP-MTXXX:~/CppDesignPatterns/build$ ./DesignPatterns/Test MyInt::MyInt(int)this: <0x7ffe7a274e30>, val: <11> MyInt::MyInt(MyInt&&)this: <0x7ffe7a274e50>, other :<0x7ffe7a274e30> MyInt::MyInt(MyInt&&)this: <0x603000000048>, other :<0x7ffe7a274e50> MyInt::~MyInt()this: <0x7ffe7a274e50> MyInt::~MyInt()this: <0x7ffe7a274e30> MyInt::MyInt(int)this: <0x7ffe7a274e70>, val: <22> MyInt::MyInt(MyInt&&)this: <0x7ffe7a274e90>, other :<0x7ffe7a274e70> MyInt::MyInt(MyInt&&)this: <0x603000000050>, other :<0x7ffe7a274e90> MyInt::~MyInt()this: <0x7ffe7a274e90> MyInt::~MyInt()this: <0x7ffe7a274e70> aptr[0] :0x603000000048 aptr[1] :0x603000000050 deleting : MyInt::~MyInt()this: <0x603000000050> MyInt::~MyInt()this: <0x603000000048> exiting onkar@DESKTOP-MTXXX:~/CppDesignPatterns/build$
编译参数
# Set C++ standards set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Disable copy elision, RVO -fno-elide-constructors #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} -g -Wall") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-elide-constructors -g")
问题分析与解答
出现两次移动构造调用的核心原因是C++14及更早标准中列表初始化动态数组的机制,再加上禁用了复制消除(-fno-elide-constructors),导致原本可以优化的步骤被强制展开:
- 初始临时对象构造:编译器首先通过
MyInt(int)构造原始临时对象(比如地址0x7ffe7a274e30的对象),对应输出的第一行。 - 移动到initializer_list元素:列表初始化依赖
std::initializer_list传递初始化元素,C++14中std::initializer_list的元素需通过移动构造生成,且禁用复制消除后无法直接在其内存位置构造对象,因此需要将第一步的临时对象移动到initializer_list的临时元素中(比如地址0x7ffe7a274e50),这是第一次移动构造。 - 移动到数组内存:最后,
std::initializer_list中的元素会被移动到动态分配的数组内存位置(比如0x603000000048,即数组第一个元素的地址),这是第二次移动构造。 - 临时对象销毁:完成移动后,
initializer_list的临时元素和原始临时对象会被依次销毁,对应输出中的前两次析构调用。
额外说明
- 由于
MyInt包含unique_ptr成员,默认拷贝构造函数被删除,因此整个过程只能依赖移动构造完成。 - 若切换到C17及更高标准,即使添加
-fno-elide-constructors参数,这些额外的移动构造也会消失。因为C17引入了保证复制消除,编译器可以直接在目标位置(数组内存)构造对象,跳过中间临时对象的创建和移动步骤。
内容的提问来源于stack exchange,提问作者Onkar N Mahajan
相关产品推荐
相关产品推荐

