You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于动态数组初始化时额外移动构造函数调用的疑问

动态数组初始化时额外移动构造调用的原因分析

问题描述

我编写了包含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),导致原本可以优化的步骤被强制展开:

  1. 初始临时对象构造:编译器首先通过MyInt(int)构造原始临时对象(比如地址0x7ffe7a274e30的对象),对应输出的第一行。
  2. 移动到initializer_list元素:列表初始化依赖std::initializer_list传递初始化元素,C++14中std::initializer_list的元素需通过移动构造生成,且禁用复制消除后无法直接在其内存位置构造对象,因此需要将第一步的临时对象移动到initializer_list的临时元素中(比如地址0x7ffe7a274e50),这是第一次移动构造。
  3. 移动到数组内存:最后,std::initializer_list中的元素会被移动到动态分配的数组内存位置(比如0x603000000048,即数组第一个元素的地址),这是第二次移动构造。
  4. 临时对象销毁:完成移动后,initializer_list的临时元素和原始临时对象会被依次销毁,对应输出中的前两次析构调用。

额外说明

  • 由于MyInt包含unique_ptr成员,默认拷贝构造函数被删除,因此整个过程只能依赖移动构造完成。
  • 若切换到C17及更高标准,即使添加-fno-elide-constructors参数,这些额外的移动构造也会消失。因为C17引入了保证复制消除,编译器可以直接在目标位置(数组内存)构造对象,跳过中间临时对象的创建和移动步骤。

内容的提问来源于stack exchange,提问作者Onkar N Mahajan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:20:33