C++:析构函数在作用域结束之前被调用?

6

我遇到了一个问题,即使析构函数在子程序范围之外定义,也会在子程序结束时调用类的析构函数。

以下是展示我的问题的最小代码:

#include <iostream>
using namespace std;

class Foo {
private:

    double *array;

public:

Foo(int N) {
   array = new double[N];
   for (int i=0; i<N; i++) {
       array[i]=0;
   }
}

~Foo() {
   delete[] array;
}
};

void subroutine(Foo x) {
   cout << "Hello!" << endl;
}

int main() {
   Foo bar(10);
   subroutine(bar);
   subroutine(bar);
}

现在,即使对象bar的作用域应该是整个main()函数,它的析构函数在第一个子例程完成后也会被调用?这意味着当我调用第二个子例程时,析构函数再次被调用,导致内存泄漏。
我发现通过在子例程中引用调用可以解决此问题,但我对此修复并不满意,因为我不明白为什么一开始它不能正常工作。有人能为我解释一下吗?
谢谢。

2
根据您的析构函数,您需要定义或删除Foo的复制构造函数和复制赋值运算符。搜索“三法则”。 - CB Bailey
5
“类的析构函数被调用” - 你会发现,如果你始终区分一个 和一个 对象,随着时间的推移,事情会变得更加清晰。析构函数是在对象上调用的,而不是在类上调用的。 - Pete Becker
4个回答

21
你正在将一个 Foo 通过值的方式传递给 subroutine 函数。这意味着它有自己的副本,在退出其作用域时被销毁。
void subroutine(Foo x) {
   // x is a new Foo here. It will get destroyed on exiting scope,
   // resulting in a destructor call
}

你的主要问题在于没有实现一个拷贝构造函数,因此动态分配的数组没有被拷贝(只有指向它的指针被复制了)。所以,当你拷贝Foo对象时,每个拷贝都指向同一个数组。而且每个拷贝都会试图销毁它。
你应该遵循三法则并实现一个赋值运算符和一个拷贝构造函数,使数组进行“深度拷贝”,这样每个Foo对象都拥有自己的数组。

啊,谢谢您的快速回复!是的,我注意到通过引用调用可以解决问题,但我不知道为什么,感谢您的解释! - Plog
1
@user1722882:实际上,最好使用std::vector并完全删除您的析构函数。重新实现标准库容器可能对于体育运动来说很不错,但当它不是练习/工作的主要目的时...它只会降低您的生产力。 - Matthieu M.

6

您将bar按值传递给子程序,因此会创建一个副本。为避免创建副本,请按引用传递:

void subroutine(Foo& x)
{
    cout << "Hello!" << endl;
}

您可以通过将复制构造函数和复制赋值运算符声明为private来防止意外复制您的类,如下所示:
class Foo {
private:

    double *array;

    Foo(const Foo&);
    Foo& operator=(const foo&);

public:
    ...
};

然后你会遇到编译错误。如果你确实需要能够复制你的类,那么你实际上需要实现这些函数来执行“深拷贝”(最好使用std::vector<float>来管理内存,包括安全复制)。


使用C++11,最好将它们声明为“deleted”。 - Francesco Dondi

3
当您调用void subroutine(Foo x) {时,您的对象bar会被复制(因此在函数完成后析构函数会被调用)。
尝试使用:void subroutine(Foo &x) {,它应该可以很好地工作。

1
你遇到的问题是你正在按值传递你的对象:
void subroutine(Foo x) {

每次调用它时,都会创建一个临时对象并调用您的对象的复制构造函数/析构函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接