为什么C ++默认析构函数不会破坏我的对象?

时间:2010-03-08 16:56:18

标签: c++ default destructor

C ++规范说默认析构函数删除所有非静态成员。然而,我无法实现这一目标。

我有这个:

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
        n = new N;
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N* n;
};

然后这应该打印给定的消息,但它不会:

M* m = new M();
delete m; //this should invoke the default destructor

13 个答案:

答案 0 :(得分:46)

默认情况下,您认为应该删除对象n指向的内容是什么?默认的析构函数会破坏指针,而不是它指向的指针。

编辑:我会看看我是否可以更清楚地说明这一点。

如果你有一个本地指针,并且它超出范围,你会指望它指向的对象被销毁吗?

{
    Thing* t = new Thing;

    // do some stuff here

    // no "delete t;"
}

清除t指针,但它指向的Thing不是。这是一个泄漏。基本上同样的事情发生在你的班上。

答案 1 :(得分:16)

想象一下这样的事情:

class M {
public:
    M() { }
//  ~M() {        // If this happens by default
//      delete n; // then this will delete an arbitrary pointer!
//  }
private:
    N* n;
};

你可以自己使用C ++中的指针。没有人会自动删除它们。

默认的析构函数确实会销毁所有成员对象。但是在这种情况下,成员对象本身就是一个指针,而不是指向它的东西。这可能会让你感到困惑。

但是,如果不是简单的内置指针,你将使用smart pointer,这种“指针”(实际上是一个类)的破坏可能会触发指向对象的破坏。

答案 2 :(得分:8)

默认析构函数正在销毁指针。如果要删除带有N默认析构函数的M,请使用智能指针。将N * n;更改为auto_ptr<N> n;n将被销毁。

编辑:正如评论中指出的那样,auto_ptr<>并不是所有用途的最佳智能指针,但它看起来就像这里所要求的那样。它具体代表所有权:M中的N在M的持续时间内不再存在。复制或分配auto_ptr<>表示所有权更改,这通常不是您想要的。如果你想从M传递指针,你应该从N *传递n.get()

更通用的解决方案是boost::shared_ptr<>,它将采用C ++ 0x标准。在任何使用原始指针的地方都可以使用它。它不是最有效的构造,并且存在循环引用的问题,但它通常是一种安全的构造。

另一个编辑:要回答另一个注释中的问题,默认析构函数的标准行为是销毁所有数据成员和基类。但是,删除原始指针只会删除指针,而不是它指向的指针。毕竟,实现无法知道这是唯一的指针,还是重要的指针,或类似的东西。智能指针背后的想法是删除智能指针至少会导致删除它所指向的内容,这通常是所期望的行为。

答案 3 :(得分:6)

当指向对象似乎属于包含的对象时,是否有任何理由使用指针?只需按值存储对象:

class M
{
    N n;

public:

    M() : n()
    {
    }
};

答案 4 :(得分:4)

说析构函数删除成员是不正确的。它调用每个成员(和基类)的析构函数,对于内置类型(如指针),它意味着什么都不做。

删除匹配是您的责任(手动或借助智能指针)。

答案 5 :(得分:3)

你的论点似乎听起来不错,但这并不是指针的工作方式。

n实际上正在被破坏,但是,这意味着正在调用N*析构函数,它不会破坏n指向的任何对象。将N*的析构函数看作是int的析构函数。它删除了它的值,对指针也是如此,它会删除它所指向的地址,但它不需要删除你刚刚删除的地址上的任何对象。

答案 6 :(得分:2)

我想你可能会对这里的间接水平感到困惑。当一个实例被销毁时,每个数据成员确实会随之被销毁。在您的情况下,当M被销毁并且M::~M()被调用时,其变量n确实被销毁。问题是nN *,所以当指针被销​​毁时,指向的东西不是。

delete不能像这样工作。考虑一下你的简单陈述:

delete n;

上述语句会破坏n指向的内容,即N类型的对象。它不会破坏n本身,它是一个N *指针。

有一个很好的理由M::~M()不会自动调用delete n;这是:所引用的N对象可能在多个M对象之间共享,并且如果一个M被摧毁,剩下的就会失去他们指向的N,到处留下可怕的悬空指针。 C ++并不试图解释你的意味着用你的指针做什么,它只是做你告诉要做的事情。

简而言之,M在它被摧毁时确实正在摧毁它的所有成员,只是这种破坏不会做你认为它应该做的事情。如果你想要一个获取对象所有权并在指针被销毁时销毁它的指针类型,请查看std::auto_ptr

答案 7 :(得分:2)

默认的析构函数如下所示:

~M()
{
}

默认的析构函数不会插入代码来执行任何有针对性的事情。如果你有 n 指向堆栈变量怎么办?自动插入删除n 会崩溃。

默认析构函数在类的每个成员(成员.~T())上调用析构函数。对于一个指针,这是一个无操作(什么都不做),就像myint一样.~int()什么都不做,但是对于定义了析构函数的成员类,会调用析构函数。

这是另一个例子:

struct MyClass {
public:
    MyClass() { .. } // doesn't matter what this does

    int x;
    int* p;
    std::string s;
    std::vector<int> v;
};

现实中的默认析构函数是这样做的:

MyClass::~MyClass()
{
    // Call destructor on member variables in reverse order
    v.~std::vector<int>(); // frees memory
    s.~std::string();      // frees memory
    p.~int*();             // does nothing, no custom destructor
    x.~int();              // does nothing, no custom destructor
}

当然,如果你定义了一个析构函数,你的析构函数中的代码会在成员变量被销毁之前运行(显然,否则它们将无效!)。

答案 8 :(得分:1)

尽量避免使用指针。它们是最后的手段。

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
       // n = new N; no need, default constructor by default
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N n; // No pointer here
};

然后以这种方式使用

main(int, char**)
{
    M m;
}

这将显示N类型的销毁对象

答案 9 :(得分:1)

我认为你可以从一个非常简单的例子中受益:

int main(int argc, char* argv[])
{
  N* n = new N();
} // n is destructed here

这也不会打印任何内容。

为什么?因为pointern)被破坏,而不是指向*n的对象。

当然,你不希望它破坏指向的对象:

int main(int argc, char* argv[])
{
  N myObject;
  {
    N* n = &myObject;
  } // n is destructed here, myObject is not

  myObject.foo();
} // myObject is destructed here

您应该记住,与C#Java等语言不同,有两种方法可以在C ++中创建对象:直接N myObject(在堆栈上)或通过new就像在new N()中一样,在这种情况下,对象放在堆上,你可以在以后释放它。

所以你的析构函数会破坏指针,但不会破坏指向的对象。如果您希望它是自动的,则在没有新对象的情况下分配对象(不使用指针)或使用Smart Pointer

答案 10 :(得分:1)

M析构函数应该'删除n'。

答案 11 :(得分:1)

由于您使用new创建实例,因此默认情况下不会删除。

答案 12 :(得分:0)

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
        n = new N;
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N* n;
};

现在的期望是:

M* m = new M();
delete m; //this should invoke the default destructor

只有在M类派生自N:

时才会发生
class M: Class N {
...

仅在这种情况下,

M* m = new M()

将调用N的构造函数,然后调用M的构造函数,其中为

delete m;

将首先自动调用M的析构函数,然后调用N