移动语义:为什么在移动的实例上调用析构函数,这是一个问题吗?
Move semantics: why is the destructor called on the moved instance and is that a problem?
我正在追赶现代 C++,练习移动语义。
我做了一个很简单的测试用例:
- 创建实例
- 移动构造一个新实例
我注意到当我的实例被销毁时,两个析构函数都被调用:
- move-constructed实例之一,其中data是有效的
指针
- 原始实例之一,数据指针被删除
并在移动时设置为 nullptr
删除 nullptr 的代码让我感到不舒服,问题如下:
- 是否(删除 nullptr)甚至是一个有效的操作(即是否会导致 UB;会不会
最终使我的应用程序崩溃)?
- 或者是我的移动构造函数/移动赋值运算符定义
错了吗?
- 我找到了这个 similar question,但仍然不清楚,特别是如果删除 nullptr 是一个问题。我应该通过检查析构函数中的指针来避免这种情况吗?如果是这样,感觉就像使用移动语义会导致某种系统性错误行为。
我的测试输出(下面的代码)是:
Test 5
new 0x7512b0
move_new 0x7512b0
delete[] 0x7512b0
delete[] 0
delete[] 0 输出是我的齿轮。
主要内容如下:
#include <iostream>
#include "test5.h"
int main()
{
std::cout << "Test 5" << std::endl;
test5 rule5;
test5 rule5move = std::move(rule5);
// rule5 = std::move(rule5move);
return 0;
}
这里是 test5.h:
#ifndef TEST5_H
#define TEST5_H
class test5
{
public:
test5(): data(new float[10]){
std::cout << "\tnew " << data << std::endl;
for (int i = 0; i < 10; i++)
data[i] = float(i);
}
~test5(){
std::cout << "\tdelete[] " << data << std::endl;
delete[] data;
}
// copy constructor
test5(const test5& t) : data(new float[10]){
std::cout << "\tcopy " << data << std::endl;
std::copy(t.data, t.data + 10, data);
}
// copy operator
test5& operator=(const test5& t){
std::cout << "\tassign " << data << std::endl;
std::copy(t.data, t.data + 10, data);
return *this;
}
// move constructor
test5(test5&& t): data(new float[10]){
delete[] data;
data = t.data;
std::cout << "\tmove_new " << data << std::endl;
t.data = nullptr;
}
// move operator
test5& operator=(test5&& t){
delete[] data;
data = t.data;
std::cout << "\tmove_assign " << data << std::endl;
t.data = nullptr;
return *this;
}
private:
float* data;
};
#endif // TEST5_H
is that (deleting nullptr) even a valid operation (i.e. does that result in UB; will it eventually crash my application)?
删除 nullptr
是空操作。这是有效的。根据网上CPP reference:
If expression evaluates to a null pointer value, no destructors are called, and the deallocation function is not called.
我认为您的移动构造函数和移动赋值运算符不正确。为什么还要使用原始指针?
如果您正在赶上现代 C++(正如您提到的),您应该使用 smart pointers。
我正在追赶现代 C++,练习移动语义。
我做了一个很简单的测试用例:
- 创建实例
- 移动构造一个新实例
我注意到当我的实例被销毁时,两个析构函数都被调用:
- move-constructed实例之一,其中data是有效的 指针
- 原始实例之一,数据指针被删除 并在移动时设置为 nullptr
删除 nullptr 的代码让我感到不舒服,问题如下:
- 是否(删除 nullptr)甚至是一个有效的操作(即是否会导致 UB;会不会 最终使我的应用程序崩溃)?
- 或者是我的移动构造函数/移动赋值运算符定义 错了吗?
- 我找到了这个 similar question,但仍然不清楚,特别是如果删除 nullptr 是一个问题。我应该通过检查析构函数中的指针来避免这种情况吗?如果是这样,感觉就像使用移动语义会导致某种系统性错误行为。
我的测试输出(下面的代码)是:
Test 5
new 0x7512b0
move_new 0x7512b0
delete[] 0x7512b0
delete[] 0
delete[] 0 输出是我的齿轮。
主要内容如下:
#include <iostream>
#include "test5.h"
int main()
{
std::cout << "Test 5" << std::endl;
test5 rule5;
test5 rule5move = std::move(rule5);
// rule5 = std::move(rule5move);
return 0;
}
这里是 test5.h:
#ifndef TEST5_H
#define TEST5_H
class test5
{
public:
test5(): data(new float[10]){
std::cout << "\tnew " << data << std::endl;
for (int i = 0; i < 10; i++)
data[i] = float(i);
}
~test5(){
std::cout << "\tdelete[] " << data << std::endl;
delete[] data;
}
// copy constructor
test5(const test5& t) : data(new float[10]){
std::cout << "\tcopy " << data << std::endl;
std::copy(t.data, t.data + 10, data);
}
// copy operator
test5& operator=(const test5& t){
std::cout << "\tassign " << data << std::endl;
std::copy(t.data, t.data + 10, data);
return *this;
}
// move constructor
test5(test5&& t): data(new float[10]){
delete[] data;
data = t.data;
std::cout << "\tmove_new " << data << std::endl;
t.data = nullptr;
}
// move operator
test5& operator=(test5&& t){
delete[] data;
data = t.data;
std::cout << "\tmove_assign " << data << std::endl;
t.data = nullptr;
return *this;
}
private:
float* data;
};
#endif // TEST5_H
is that (deleting nullptr) even a valid operation (i.e. does that result in UB; will it eventually crash my application)?
删除 nullptr
是空操作。这是有效的。根据网上CPP reference:
If expression evaluates to a null pointer value, no destructors are called, and the deallocation function is not called.
我认为您的移动构造函数和移动赋值运算符不正确。为什么还要使用原始指针?
如果您正在赶上现代 C++(正如您提到的),您应该使用 smart pointers。