什么时候应该为我的课堂提供析构函数? [英] When should I provide a destructor for my class?

查看:70
本文介绍了什么时候应该为我的课堂提供析构函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这似乎是一个相当琐碎的问题,或者至少是一个普遍的问题,但是我在Google或SO上找不到令人满意的答案.

This seems like a rather trivial or at least common question, but I couldn't find a satisfying answer on google or on SO.

我不确定何时应该为我的班级实施析构函数.

I'm not sure when I should implement a destructor for my class.

一个明显的例子是,当类将连接包装到文件时,我想确保连接已关闭,所以我在析构函数中将其关闭.

An obvious case is when the class wraps a connection to a file, and I want to make sure the connection is closed so I close it in the destructor.

但是我想大体上知道如何定义析构函数.有什么准则可以检查我是否应该在此类中使用析构函数?

But I want to know in general, how can I know if I should define a destructor. What guidelines are there that I can check to see if I should have a destructor in this class?

我能想到的一个这样的准则是,该类是否包含任何成员指针.默认的析构函数将销毁删除时的指针,但不会销毁它们指向的对象.因此,这应该是用户定义的析构函数的工作.例如:(我是C ++新手,因此该代码可能无法编译).

One such guideline I can think of, is if the class contains any member pointers. The default destructor would destory the pointers on deletion, but not the objects they're pointing at. So that should be the work of a user-defined destructor. E.g: (I'm a C++ newbie, so this code might not compile).

class MyContainer {
public:
    MyContainer(int size) : data(new int[size]) { }
    ~MyContainer(){
        delete [] data;
    }
    // .. stuff omitted
private:
    int* data;
}

如果我没有提供该析构函数,那么销毁MyContainer对象将意味着造成泄漏,因为以前data引用的所有数据都不会被删除.

If I hadn't supplied that destructor, than destroying a MyContainer object would mean creating a leak, since all the data previously referenced by data wouldn't have been deleted.

但是我有两个问题:

1-这是唯一的指南"吗? IE.定义一个析构函数,如果该类具有成员指针或它正在管理资源?还是还有什么?

1- Is this the only 'guideline'? I.e. define a destructor if the class has member pointers or if it's managing a resource? Or is there anything else?

2-在某些情况下,我应该 delete成员指针吗?那参考呢?

2- Are there cases when I should not delete member pointers? What about references?

推荐答案

如果默认销毁不能满足需要,则需要定义一个析构函数.当然,这只是一个问题:默认析构函数做什么?好吧,它调用了每个成员变量的析构函数,仅此而已.如果这对您来说足够,那么您就很好了.如果不是,那么您需要编写一个析构函数.

You need to define a destructor if the default destruction does not suffice. Of course, this just punts the question: what does the default destructor do? Well, it calls the destructors of each of the member variables, and that's it. If this is enough for you, you're good to go. If it's not, then you need to write a destructor.

最常见的示例是使用new分配指针的情况.指针(指向任何类型)是一个基元,并且析构函数只是使指针本身消失,而无需接触指向内存的指针.因此,指针的默认析构函数对我们而言没有正确的行为(它将泄漏内存),因此我们需要在析构函数中进行delete调用.想象一下,现在我们将原始指针更改为智能指针.当智能指针被销毁时,它还会调用其指向的析构函数,然后释放内存.因此,智能指针的析构函数就足够了.

The most common example is the case of allocating a pointer with new. A pointer (to any type) is a primitive, and the destructor just makes the pointer itself go away, without touching the pointed to memory. So the default destructor of a pointer does not have the right behavior for us (it will leak memory), hence we need a delete call in the destructor. Imagine now we change the raw pointer to a smart pointer. When the smart pointer is destroyed, it also calls the destructor of whatever its pointing to, and then frees the memory. So a smart pointer's destructor is sufficient.

通过了解最常见情况的根本原因,您可以推断出不太常见的情况.的确,如果您使用的是智能指针和标准库容器,它们的析构函数通常会做正确的事情,而您根本不需要编写析构函数.但是仍然有例外.

By understanding the underlying reason behind the most common case, you can reason about less common cases. It's true that very often, if you're using smart pointers and std library containers, their destructors do the right thing and you don't need to write a destructor at all. But there are still exceptions.

假设您有一个Logger类.但是,该记录器类很聪明,它将一堆消息缓冲到Log,然后仅在缓冲区达到一定大小(清空"缓冲区)时才将它们写到文件中.这比直接将所有内容立即转储到文件中更有效.当Logger被销毁时,无论缓冲区是否已满,都需要从缓冲区中清除所有内容,因此,即使它很容易以std :: vector和std的方式实现Logger,也可能要为其编写一个析构函数. :: string,因此销毁后不会泄漏任何内容.

Suppose you have a Logger class. This logger class is smart though, it buffers up a bunch of messages to Log, and then writes them out to a file only when the buffer reaches a certain size (it "flushes" the buffer). This can be more performant than just dumping everything to a file immediately. When the Logger is destroyed, you need to flush everything from the buffer regardless of whether it's full, so you'll probably want to write a destructor for it, even though its easy enough to implement Logger in terms of std::vector and std::string so that nothing leaks when its destroyed.

我没有看到问题2.问题2的答案是,如果它是一个非所有者指针,则不应调用delete.换句话说,如果某个其他类或作用域仅负责清理此对象之后,并且您拥有指针只是看",则不要调用delete.原因是,如果您调用delete并由其他人拥有,则指针会两次被调用delete:

I didn't see question 2. The answer to question 2 is that you should not call delete if it is a non-owning pointer. In other words, if some other class or scope is solely responsible for cleaning up after this object, and you have the pointer "just to look", then do not call delete. The reason why is if you call delete and somebody else owns it, the pointer gets delete called on it twice:

struct A {
  A(SomeObj * obj) : m_obj(obj){};
  SomeObj * m_obj;
  ~A(){delete m_obj;};
}

SomeObj * obj = new SomeObj();
A a(obj);
delete obj; // bad!

实际上,可以说c ++ 11中的指导方针是永远不要在指针上调用delete.为什么?好吧,如果您在指针上调用delete,则意味着您拥有它.而且,如果您拥有它,那么就没有理由不使用智能指针,尤其是unique_ptr实际上具有相同的速度并且可以自动执行,并且更有可能是线程安全的.

In fact, arguably the guideline in c++11 is to NEVER call delete on a pointer. Why? Well, if you call delete on a pointer, it means you own it. And if you own it, there's no reason not to use a smart pointer, in particular unique_ptr is virtually the same speed and does this automatically, and is far more likely to be thread safe.

此外,(请原谅,我现在真的很了解),将其他对象的对象(原始指针或引用)的非所有者视图作为对象通常是一个坏主意.为什么?因为带有原始指针的对象可能不必担心销毁另一个对象,因为它不拥有该对象,但是它无法知道何时销毁它.指向对象的对象仍可以在带有指针的对象还活着的情况下被破坏:

Further, furthermore (forgive me I'm getting really into this now), it's generally a bad idea to make non-owning views of objects (raw pointers or references) members of other objects. Why? Because, the object with the raw pointer may not have to worry about destroying the other object since it doesn't own it, but it has no way of knowing when it will be destroyed. The pointed to object could be destroyed while the object with the pointer is still alive:

struct A {
  SomeObj * m_obj;
  void func(){m_obj->doStuff();};
}

A a;
if(blah) {
  SomeObj b;
  a.m_obj = &b;
}
a.func() // bad!

请注意,这仅适用于对象的成员字段.将对象的视图传递给函数(成员或非成员)是安全的,因为在对象本身的封闭范围内调用了该函数,所以这不是问题.

Note that this only applies to member fields of objects. Passing a view of an object into a function (member or not) is safe, because the function is called in the enclosing scope of the object itself, so this is not an issue.

所有这一切的残酷结论是,除非您知道自己在做什么,否则就永远不应该将原始指针或引用作为对象的成员字段.

The harsh conclusion of all this is that unless you know what you're doing, you just shouldn't ever have raw pointers or references as member fields of objects.

我想总体结论(这真是太好了!)是,一般而言,您的类应以不需要析构函数的方式编写,除非析构函数进行了语义上有意义的事情.在我的Logger示例中,必须刷新Logger,在销毁之前必须发生一些重要的事情.您不应该编写(通常)需要对其成员进行简单清理的类,而成员变量应该在对其进行清理之后.

Edit 2: I guess the overall conclusion (which is really nice!) is that in general, your classes should be written in such a way that they don't need destructors unless the destructors do something semantically meaningful. In my Logger example, the Logger has to be flushed, something important has to happen before destruction. You should not write (generally) classes that need to do trivial clean-up after their members, member variables should clean up after themselves.

这篇关于什么时候应该为我的课堂提供析构函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆