第一次尝试使用std::move优化代码,就遇到了坑
简化代码如下:
class moveTest
{
public:
moveTest() { std::cout << "construct moveTest" << std::endl; };
~moveTest() {
if ((buf) != NULL) delete[](buf); (buf) = NULL;
std::cout << "delete moveTest" << std::endl;
};
void initbuf() { buf = new int[10]; };
private:
int* buf = nullptr;
};
void push(moveTest& m)
{
moveTest testA = std::move(m);
};
int main()
{ moveTest b;
b.initbuf();
push(b);
return 0;
}
上述代码运行时,报错。错误原因 moveTest对象析构出发断点
解释如下:
根据您提供的代码,错误原因是因为在A
类中的push
函数中,将moveTest
对象m
移动赋值给testA
成员变量时,会调用moveTest
类的移动赋值运算符。
然而,根据您提供的代码,moveTest
类没有显式定义移动赋值运算符。这会导致使用默认的移动赋值运算符,而默认的移动赋值运算符会简单地将成员变量逐个移动赋值,而不会处理指针成员变量。
在moveTest
类中,存在一个指针成员变量buf
,在默认的移动赋值运算符中,只会简单地将指针成员变量复制到目标对象中。这会导致两个对象的buf
指向同一块内存,当其中一个对象被销毁时,会释放内存,导致另一个对象的buf
成为悬空指针。
因此,当moveTest
对象m
在push
函数中被移动赋值给testA
成员变量后,m
对象的析构函数被调用,尝试释放内存,但由于buf
成为悬空指针,导致触发断点。
要解决这个问题,您可以在moveTest
类中显式定义移动赋值运算符,确保正确处理指针成员变量的移动赋值。例如:
moveTest& operator=(moveTest&& other)
{
if (this != &other)
{
delete[] buf;
buf = other.buf;
other.buf = nullptr;
}
return *this;
}
通过显式定义移动赋值运算符,确保在移动赋值时正确处理buf
指针,避免悬空指针的问题。这样,当moveTest
对象被移动赋值时,不会触发断点。
悬空指针是指指向已释放或无效的内存的指针。当我们尝试使用悬空指针时,就会导致未定义行为,这可能包括访问无效的内存、触发段错误、导致程序崩溃等。
在您的代码中,当moveTest
对象m
在push
函数中被移动赋值给testA
成员变量后,m
对象的析构函数被调用。在析构函数中,您尝试释放指针成员变量buf
所指向的内存:
~moveTest() {
if ((buf) != NULL) delete[](buf); (buf) = NULL;
std::cout << "delete moveTest" << std::endl;
};
然而,在移动赋值运算符中,您没有将buf
指针设置为nullptr
,而是将其赋值给了目标对象,导致m
对象的buf
成为悬空指针。因此,在析构函数中尝试释放悬空指针时,会触发断点。
触发断点的具体原因可能是由于调试器检测到了悬空指针的释放,而断点是调试器的一种行为,用于提醒开发人员可能存在的问题。
为了解决这个问题,需要在移动赋值运算符中将buf
指针设置为nullptr
,以避免在析构函数中释放悬空指针。
正确代码如下:
class moveTest
{
public:
moveTest() { std::cout << "construct moveTest" << std::endl; };
~moveTest() {
if ((buf) != NULL) delete[](buf); (buf) = NULL;
std::cout << "delete moveTest" << std::endl;
};
void initbuf() { buf = new int[10]; };
// 移动构造函数
moveTest(moveTest&& other) noexcept {
buf = other.buf;
other.buf = nullptr;
}
// 移动赋值运算符
moveTest& operator=(moveTest&& other) noexcept {
if (this != &other) {
delete[] buf;
buf = other.buf;
other.buf = nullptr;
}
return *this;
}
private:
int* buf = nullptr;
};
为了保证 buf
在移动构造函数中被正确处理,需要在移动构造函数中将源对象的 buf
指针设置为 nullptr
,并将资源所有权转移给新对象。以下是一个示例:
class moveTest {
public:
moveTest() { std::cout << "construct moveTest" << std::endl; };
~moveTest() {
if (buf != nullptr) delete[] buf;
std::cout << "delete moveTest" << std::endl;
};
void initbuf() {
buf = new int[10];
};
// 自定义移动构造函数
moveTest(moveTest&& other) noexcept : buf(other.buf) {
std::cout << "moveTest move constructor called" << std::endl;
other.buf = nullptr; // 将源对象的 buf 指针设置为 nullptr
}
private:
int* buf = nullptr;
int a, b, c, d, e, f;
};
在上述代码中,我们在 moveTest
类中添加了一个自定义的移动构造函数。在移动构造函数中,我们将源对象的 buf
指针赋值给新对象的 buf
指针,并将源对象的 buf
指针设置为 nullptr
,以确保资源所有权正确转移。
以下是一个使用自定义移动构造函数的示例:
int main() {
moveTest obj1;
obj1.initbuf();
moveTest obj2(std::move(obj1)); // 使用移动构造函数将 obj1 的值移动到 obj2 中
return 0;
}
在上述代码中,我们首先创建了一个名为 obj1
的 moveTest
对象,并调用了 initbuf
方法来初始化 buf
数组。然后,我们使用移动构造函数将 obj1
的值移动到 obj2
中。在移动构造函数中,obj1
的 buf
指针被设置为 nullptr
,而 obj2
的 buf
指针指向了原来的 buf
数组。最后,当程序结束时,会调用 obj1
和 obj2
的析构函数,释放 buf
数组的内存。
运行上述代码,将会输出以下内容:
construct moveTest
moveTest move constructor called
delete moveTest
delete moveTest
这表明自定义的移动构造函数成功地将 buf
的所有权从 obj1
转移到了 obj2
,并正确地处理了 buf
的释放。
在自定义移动构造函数中,成员变量 a、b 和 c 不需要显式处理,因为它们是基本类型(如 int)的成员变量,可以直接进行移动。当对象被移动时,这些基本类型的成员变量会自动从源对象移动到目标对象中。
在移动构造函数中,只需要处理拥有资源所有权的成员变量,例如指针或动态分配的内存。对于基本类型的成员变量,编译器会自动处理移动操作。