在下面的代码中,我发现在给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 createdn" << std::endl;
std::any a = n1;
std::cout << "n1 assigned to an"<< std::endl;
a = n2;
std::cout << "n2 assigned to an"<< std::endl;
std::cout << "MyStruct objects from beginning and mystruct object held by a are going to be destructedn"<< 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)
在上面的代码中,根据标准,由于我声明了构造函数和析构函数,所以复制构造函数和复制赋值是编译器默认的。我希望看到的顺序:
- 如预期:我希望看到我创建的两个对象
n1
和n2
的两个构造函数的调用。 - 如预期:我将
n1
分配给a
,我期望调用复制构造函数,我将无法看到,因为它是编译器默认的。 - 如预期:我将
n2
分配给a
,因此我希望再次调用复制构造函数,并为先前值为mx
35的对象包含析构函数。我希望调用的顺序是相反的,所以首先是析构,然后是复制构造。我们还可以清楚地看到,当将n2
赋值给a
时,为值为mx
47的对象调用了析构函数。我不希望看到这个,除非在屏幕后面我的n2
对象首先被复制到某种类型的临时对象。 - 如预期:我希望
a
中包含的对象和我创建的n1
和n2
中的2个对象以创建的相反顺序被销毁,因为它们超出了作用域。
更让我困惑的是,当我自实现其他特殊成员函数时,这种行为发生了变化。无论只取消复制构造函数的注释,还是取消其他特殊成员函数的注释,在将n2
赋值给a
时,输出~MyStruct(47)
都会消失。如果上面代码中至少没有注释复制构造函数,则输出如下:
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 the beginning and the mystruct object held by a are going to be destructed
~MyStruct(47)
~MyStruct(47)
~MyStruct(35)
所以我的具体问题如下:
- 为什么在第一种情况下为
mx
47对象调用析构函数? - 为什么在分配
n2
而不是之前调用复制构造函数之后调用析构函数? - 为什么
mx
值为47的对象的析构函数在至少添加复制构造函数时突然停止调用?
以上代码已使用c++、Microsoft和Clang编译器编译,适用于c++ 17和c++ 20标准。
指定any
的赋值操作符为:
Effects:构造一个类型为
any
的对象tmp
,该对象包含一个由std::forward<T>(rhs)
和tmp.swap(*this)
直接初始化的类型为VT
的对象。如果抛出异常则不影响。
libstdc++所做的是将any = n2;
计算为any = std::any(n2);
。这个临时的any
在语句的末尾被销毁,这就是你意外的析构函数打印的来源。
现在,any
有一个优化:它并不总是分配它所持有的对象。如果类型足够小并且是no-throw move可构造的,则对象将在any
中就地构造,从而节省分配。最初,您的类型满足了这一要求。但是,当您显式添加move构造函数时,它不是noexcept
,因此必须分配any
。然而,这种分配的结果是,对any
进行move赋值可以直接接管右边的指针,而不必移动对象并破坏原始对象。因此,在这种情况下,没有析构函数(或move)。
但是如果您添加了noexcept
,那么您将同时看到析构函数和move。