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

为何赋值给std::any对象时析构函数会被调用?

std::any赋值时的意外析构调用及显式拷贝构造的影响

编写了如下代码,发现赋值给std::any对象时会出现意外的析构函数调用,而显式声明并实现拷贝构造函数后,该析构调用会消失。

#include <iostream>
#include <any>

struct MyStruct {
    int mx;

    MyStruct(int x) : mx{x} {
        std::cout << "MyStruct(" << x << ")" << std::endl;
    }

//     MyStruct(const MyStruct& other) {
//         std::cout << "Copy constructor (" << other.mx << ")" << std::endl;
//         mx = other.mx;
//     }
//
//    MyStruct& operator=(const MyStruct& other) {
//        std::cout << "Copy assignment (" << other.mx << ")" << std::endl;
//        mx = other.mx;
//        return *this;
//    }
//
//    MyStruct(MyStruct&& other) {
//        std::cout << "Move constructor (" << other.mx << ")" << std::endl;
//        mx = other.mx;
//    }
//
//    MyStruct& operator=(MyStruct&& other) {
//        std::cout << "Move assignment (" << other.mx << ")" << std::endl;
//        mx = other.mx;
//        return *this;
//    }

    ~MyStruct()
    {
        std::cout << "~MyStruct(" << mx << ")" << "\n";
    }
};

int main()
{
    MyStruct n1{35};
    MyStruct n2{47};
    std::cout << "MyStruct objects were created\n" << std::endl;

    std::any a = n1;
    std::cout << "n1 assigned to a\n"<< std::endl;

    a = n2;
    std::cout << "n2 assigned to a\n"<< std::endl;

    std::cout << "MyStruct objects from beginning and mystruct object held by a are going to be destructed\n"<< std::endl;
}

未显式实现拷贝构造时的输出

MyStruct(35)
MyStruct(47)
MyStruct objects were created

n1 assigned to a

~MyStruct(35)
~MyStruct(47)
n2 assigned to a


MyStruct objects from the beginning and the mystruct object held by a are going to be destructed

~MyStruct(47)
~MyStruct(47)
~MyStruct(35)

根据C++标准,由于声明了构造函数和析构函数,编译器会默认生成拷贝构造函数和拷贝赋值运算符。预期行为:

  • 正常调用n1和n2的构造函数;
  • 将n1赋值给a时调用拷贝构造函数(因默认生成无法观测);
  • 将n2赋值给a时,应先销毁a中原有mx=35的对象,再调用拷贝构造函数,但实际出现了mx=47的对象析构调用;
  • 程序结束时,a中的对象、n1和n2按创建逆序析构。

显式实现拷贝构造后的输出

MyStruct(35)
MyStruct(47)
MyStruct objects were created

Copy constructor (35)
n1 assigned to a

Copy constructor (47)
~MyStruct(35)
n2 assigned to a

MyStruct objects from beginning and mystruct object held by a are going to be destructed

~MyStruct(47)
~MyStruct(47)
~MyStruct(35)

具体问题

  1. 第一种情况中,mx=47的对象析构函数为何被调用?
  2. 赋值n2给a时,析构函数为何在拷贝构造之后调用而非之前?
  3. 为何至少添加拷贝构造函数后,mx=47的对象析构调用会消失?

(注:代码已在g++、MSVC、Clang编译器的C17和C20标准下编译验证)


解答

问题1:mx=47的析构调用来源

当你没有显式定义拷贝构造函数时,编译器生成的拷贝构造是隐式声明的。std::any的实现在处理这种隐式声明拷贝构造的类型时,无法直接在其内部存储中使用placement new构造新对象,而是会先创建一个临时的MyStruct对象(拷贝n2得到mx=47的实例),再将这个临时对象拷贝到std::any的内部存储中。临时对象完成使命后被销毁,就产生了~MyStruct(47)的调用。而~MyStruct(35)则是销毁std::any中原先持有的n1拷贝。

问题2:析构在拷贝构造之后的原因

这是std::any赋值操作的异常安全策略:先构造新的对象(无论是临时对象还是在内部存储中构造),确保新对象构造成功后,再销毁旧对象。如果新对象构造时抛出异常,旧对象仍能保持有效状态,避免程序陷入不一致的错误状态。这种"先构造、后销毁"的顺序是标准库容器和智能指针等类型的常见安全实践。

问题3:显式拷贝构造消除额外析构的原因

当你显式实现拷贝构造函数后,该函数变为用户声明的特殊成员函数。std::any的类型特性检测会识别到这一点,允许直接在其已分配的内部存储中使用placement new拷贝构造新的MyStruct对象,无需创建临时对象。此时赋值流程变为:先拷贝构造新对象到内部存储,再销毁旧对象,因此不会产生临时对象的额外析构调用。

内容的提问来源于stack exchange,提问作者MasterMind

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 23:25:26