正确使用析构函数
Right usage of destructor
我刚开始使用 C++,现在我有一个非常基本的问题。
我写了 2 classes:
坐标:
#include <stdio.h>
class Coordinate {
private:
int x;
int y;
public:
Coordinate(int a, int b) {
x = a;
y = b;
};
void printTest() {
printf("%d %d\n", x, y);
};
};
测试:
class Test {
private:
int q;
Coordinate *point;
public:
Test(int a, int b, int c) {
q = a;
point = new Coordinate(b, c);
};
virtual ~Test() {
delete point;
}
};
主要功能:
int main() {
Test *test = new Test(1, 2, 3);
// ...
delete test;
return 0;
}
在我的 main
中,我使用了 Test
class 的对象。我写了自己的 Test
析构函数,但我不确定这个析构函数是否按预期工作。它是否完全释放了 test
的内存?还是我必须对 q
属性做些什么才能释放它?
是的,这是 C++ 析构函数的正确用法(您在 Test 中的析构函数不需要 virtual
关键字,因为您的示例中没有继承)。
根据经验,每个 new
后面都应该跟一个 delete
,但是当您使用 class 和实例化时,它会变得更加微妙。在 Rule of Three or Five 之后,当 class 使用动态内存时,您应该重新定义 class 析构函数 以相应地释放,您所做的,太棒了!
在您的程序执行中,当调用 delete test
时,它将首先释放 point
的动态内存,然后再释放您在 main 函数中使用测试属性设置的动态内存。通过这种方式,您不会泄漏内存(耶!)并且您的内存管理已相应完成:)
就目前而言,您所做的是正确的。 Valgrind 报告
==28151== HEAP SUMMARY:
==28151== in use at exit: 0 bytes in 0 blocks
==28151== total heap usage: 3 allocs, 3 frees, 72,736 bytes allocated
==28151==
==28151== All heap blocks were freed -- no leaks are possible
您缺少的是编译器为您提供了默认的复制构造函数和赋值运算符。这些将复制指针,而不是创建一个新的指向值,所以任何时候你复制一个Test
对象你会然后有两个对象,其析构函数 都将尝试删除相同的存储 。这是一个双重免费,它会毁了你的一天。
为了避免这种情况,C++ 程序员使用 Rule of Three or Rule of Five when writing classes, or - even better - the Rule of Zero,它说你不应该做任何 new
或 delete
除了class 只为拥有存储空间而存在。
您需要一个复制构造函数来确保内存管理正常。
因为隐式生成的构造函数和赋值运算符只是简单地复制所有 class 数据成员 ("shallow copy")。
由于您的 class 中有分配数据的指针,因此您确实需要它。
例如,如果在你的主代码部分:// ...
你做了一个副本:
Test testB = *test;
testB
有一个 Coordinate
指针指向与 *test
相同的内存区域。这可能会导致问题,例如,当 testB
超出范围时,它将释放 *test
正在使用的相同内存。
复制构造函数应该如下所示:
Test(const Test& other)
: point (new Coordinate(other.x, other.y))
, q(other.q)
{
}
有了这个,您将确保每个 Coordinate*
都可以正常初始化并正常发布。
在你的情况下,你不需要指针
class Test {
private:
int q;
Coordinate point;
public:
Test(int a, int b, int c) : q(a), point(b, c) {};
};
int main() {
Test test(1, 2, 3);
// ...
return 0;
}
足够了。
如果要分配内存,强烈建议改用智能指针或容器:
class Test {
private:
int q;
std::unique_ptr<Coordinate> point;
public:
Test(int a, int b, int c) : q(a), point(std::make_unique_ptr<Coordinate>(b, c)) {};
};
int main() {
auto test = std::make_unique<Test>(1, 2, 3);
// ...
return 0;
}
无论哪种方式,您都遵守 3/5/0 规则。
在第二种情况下,您可能应该想为您的 class:
提供复制构造函数
Test(const Test& rhs) : q(rhs.q), point(std::make_unique<Coordinate>(*rhs.point)) {}
我刚开始使用 C++,现在我有一个非常基本的问题。
我写了 2 classes:
坐标:
#include <stdio.h>
class Coordinate {
private:
int x;
int y;
public:
Coordinate(int a, int b) {
x = a;
y = b;
};
void printTest() {
printf("%d %d\n", x, y);
};
};
测试:
class Test {
private:
int q;
Coordinate *point;
public:
Test(int a, int b, int c) {
q = a;
point = new Coordinate(b, c);
};
virtual ~Test() {
delete point;
}
};
主要功能:
int main() {
Test *test = new Test(1, 2, 3);
// ...
delete test;
return 0;
}
在我的 main
中,我使用了 Test
class 的对象。我写了自己的 Test
析构函数,但我不确定这个析构函数是否按预期工作。它是否完全释放了 test
的内存?还是我必须对 q
属性做些什么才能释放它?
是的,这是 C++ 析构函数的正确用法(您在 Test 中的析构函数不需要 virtual
关键字,因为您的示例中没有继承)。
根据经验,每个 new
后面都应该跟一个 delete
,但是当您使用 class 和实例化时,它会变得更加微妙。在 Rule of Three or Five 之后,当 class 使用动态内存时,您应该重新定义 class 析构函数 以相应地释放,您所做的,太棒了!
在您的程序执行中,当调用 delete test
时,它将首先释放 point
的动态内存,然后再释放您在 main 函数中使用测试属性设置的动态内存。通过这种方式,您不会泄漏内存(耶!)并且您的内存管理已相应完成:)
就目前而言,您所做的是正确的。 Valgrind 报告
==28151== HEAP SUMMARY:
==28151== in use at exit: 0 bytes in 0 blocks
==28151== total heap usage: 3 allocs, 3 frees, 72,736 bytes allocated
==28151==
==28151== All heap blocks were freed -- no leaks are possible
您缺少的是编译器为您提供了默认的复制构造函数和赋值运算符。这些将复制指针,而不是创建一个新的指向值,所以任何时候你复制一个Test
对象你会然后有两个对象,其析构函数 都将尝试删除相同的存储 。这是一个双重免费,它会毁了你的一天。
为了避免这种情况,C++ 程序员使用 Rule of Three or Rule of Five when writing classes, or - even better - the Rule of Zero,它说你不应该做任何 new
或 delete
除了class 只为拥有存储空间而存在。
您需要一个复制构造函数来确保内存管理正常。 因为隐式生成的构造函数和赋值运算符只是简单地复制所有 class 数据成员 ("shallow copy")。 由于您的 class 中有分配数据的指针,因此您确实需要它。
例如,如果在你的主代码部分:// ...
你做了一个副本:
Test testB = *test;
testB
有一个 Coordinate
指针指向与 *test
相同的内存区域。这可能会导致问题,例如,当 testB
超出范围时,它将释放 *test
正在使用的相同内存。
复制构造函数应该如下所示:
Test(const Test& other)
: point (new Coordinate(other.x, other.y))
, q(other.q)
{
}
有了这个,您将确保每个 Coordinate*
都可以正常初始化并正常发布。
在你的情况下,你不需要指针
class Test {
private:
int q;
Coordinate point;
public:
Test(int a, int b, int c) : q(a), point(b, c) {};
};
int main() {
Test test(1, 2, 3);
// ...
return 0;
}
足够了。
如果要分配内存,强烈建议改用智能指针或容器:
class Test {
private:
int q;
std::unique_ptr<Coordinate> point;
public:
Test(int a, int b, int c) : q(a), point(std::make_unique_ptr<Coordinate>(b, c)) {};
};
int main() {
auto test = std::make_unique<Test>(1, 2, 3);
// ...
return 0;
}
无论哪种方式,您都遵守 3/5/0 规则。
在第二种情况下,您可能应该想为您的 class:
提供复制构造函数Test(const Test& rhs) : q(rhs.q), point(std::make_unique<Coordinate>(*rhs.point)) {}