2

一般来说,如果你有一个类继承自一个Thread类,并且你希望该类的实例在它们完成运行后自动释放,可以delete this吗?

具体例子:

在我的应用程序中,我有一个Timer类,其中包含一个static名为schedule. 用户这样称呼它:

Timer::schedule((void*)obj, &callbackFunction, 15); // call callbackFunction(obj) in 15 seconds

schedule方法创建一个Task对象(其目的类似于 Java TimerTask 对象)。类Task属于privateTimer并继承自Thread类(使用 pthreads 实现)。所以该schedule方法这样做:

Task *task = new Task(obj, callback, seconds);
task->start(); // fork a thread, and call the task's run method

构造Task函数保存参数以在新线程中使用。在新线程中,任务的run方法被调用,如下所示:

void Timer::Task::run() {
    Thread::sleep(this->seconds);
    this->callback(this->obj);
    delete this;
}

请注意,我不能使该task对象成为堆栈分配的对象,因为新线程需要它。另外,我已将Task课程private设置为Timer课程以防止其他人使用它。

我特别担心,因为删除Task对象意味着删除底层Thread对象。Thread对象中的唯一状态是pthread_t变量。有什么办法可以回来咬我吗?请记住,方法完成后我不使用该pthread_t变量。run

delete this我可以通过引入某种状态(通过Thread::start方法的参数或构造函数中的某些东西)来绕过调用,Thread这表明被分叉的方法应该delete是它正在调用该run方法的对象。但是,代码似乎按原样工作。

有什么想法吗?

4

4 回答 4

3

我认为“删除这个”是安全的,只要您之后在 run() 方法中不做任何其他事情(因为此时所有任务对象的成员变量等都将被释放内存)。

不过,我确实想知道您的设计……您真的想在每次有人安排计时器回调时产生一个新线程吗?这对我来说似乎效率很低。您可能会考虑使用线程池(或者甚至只是一个持久计时器线程,它实际上只是一个大小为 1 的线程池),至少作为以后的优化。(或者更好的是,实现计时器功能而不产生额外的线程......如果您使用具有超时功能的事件循环(如 select() 或 WaitForMultipleObjects()),则可以多路复用任意数量的独立单个线程的事件循环中的计时器事件)

于 2010-02-09T06:28:37.167 回答
2

delete this;只要你保证,没有什么特别可怕的:

  1. 对象总是动态分配的,并且
  2. 对象的任何成员在被删除后都不会被使用。

其中第一个是困难的。您可以采取一些措施(例如,将 ctor 设为私有)有所帮助,但如果有人足够努力,几乎可以绕过您所做的任何事情。

也就是说,使用某种线程池可能会更好。它往往更高效和可扩展。

编辑:当我谈到被绕过时,我在想这样的代码:

class HeapOnly {
  private:
    HeapOnly () {} // Private Constructor.
    ~HeapOnly () {} // A Private, non-virtual destructor.
  public:
    static HeapOnly * instance () { return new HeapOnly(); }
    void destroy () { delete this; }  // Reclaim memory.
};

这与我们可以提供的保护一样好,但绕过它是微不足道的:

int main() { 
    char buffer[sizeof(HeapOnly)];

    HeapOnly *h = reinterpret_cast<HeapOnly *>(buffer);
    h->destroy(); // undefined behavior...
    return 0;
}

这样直接的时候,这种情况就很明显了。当它分布在一个更大的系统上时,(例如)一个对象工厂实际生产对象,并在其他地方编写完全分配内存的代码等,追踪起来会变得更加困难。

我最初说“没有什么特别可怕的delete this;”,我坚持这一点——我不会回过头来说不应该使用它。我试图警告如果其他代码“不能很好地与其他代码一起使用”,它可能会出现什么样的问题。

于 2010-02-09T06:32:13.950 回答
1

如果你对一个Task对象所做的只是new它,start它,然后是delete它,你为什么还需要一个对象呢?为什么不简单地实现一个功能start(减去对象创建和删除)?

于 2010-02-09T06:54:17.830 回答
1

delete this释放您明确分配给线程使用的内存,但是操作系统或 pthreads 库分配的资源呢,例如线程的调用堆栈和内核线程/进程结构(如果适用)?如果您从不打电话pthread_join()pthread_detach()从不设置detachstate,我认为您仍然存在内存泄漏。

这还取决于您的Thread课程是如何设计使用的。如果它调用pthread_join()它的析构函数,那就有问题了。

如果你使用pthread_detach()(你的Thread对象可能已经在做),并且你小心不要在 deletethis之后取消引用this,我认为这种方法应该是可行的,但其他人建议使用寿命更长的线程(或线程池)很好值得考虑。

于 2010-02-09T16:01:14.437 回答