Можно ли использовать «удалить это»? на объекте, который наследуется от класса Thread? - PullRequest
2 голосов
/ 09 февраля 2010

В общем, если у вас есть класс, который наследуется от класса Thread, и вы хотите, чтобы экземпляры этого класса автоматически освобождали его после завершения работы, можно ли delete this?

Конкретный пример:

В моем приложении у меня есть класс Timer с одним static методом с именем schedule. Пользователи называют это так:

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

Метод schedule создает объект Task (который по своей цели аналогичен объекту Java TimerTask). Класс Task равен private классу Timer и наследуется от класса 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 ]

3 голосов
/ 09 февраля 2010

Я думаю, что «удалить это» безопасно, если вы потом ничего не сделаете в методе run () (потому что все переменные-члены объекта Задачи и т. Д. Будут освобождены памятью в этот момент).

Я действительно удивляюсь твоему дизайну, хотя ... ты действительно хочешь создавать новый поток каждый раз, когда кто-то планирует таймер обратного вызова?Это кажется мне неэффективным.Возможно, вы захотите использовать пул потоков (или даже только один постоянный поток таймера, который на самом деле представляет собой просто пул потоков первого размера), по крайней мере, в качестве оптимизации для дальнейшего использования.(или, что еще лучше, реализуйте функцию таймера, не создавая дополнительных потоков вообще ... если вы используете цикл обработки событий с функцией тайм-аута (например, select () или WaitForMultipleObjects ()), можно мультиплексировать произвольное количество независимыхсобытия таймера в цикле событий одного потока)

2 голосов
/ 09 февраля 2010

Нет ничего особенно ужасного в 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; нет ничего особенно ужасного», и я поддерживаю это - я не вернусь к этому и не скажу, что его не следует использовать. Я пытаюсь предупредить о типе проблемы, которая может возникнуть с ним, если другой код «не подходит другим».

1 голос
/ 09 февраля 2010

delete this освобождает память, которую вы явно выделили для использования потоком, но как насчет ресурсов, выделенных ОС или библиотекой pthreads, таких как стек вызовов потока и структура потока / процесса ядра (если применимо)? Если вы никогда не звоните pthread_join() или pthread_detach() и никогда не устанавливаете detachstate , я думаю, у вас все еще есть утечка памяти.

Это также зависит от того, как ваш класс Thread предназначен для использования. Если он вызывает pthread_join() в своем деструкторе, это проблема.

Если вы используете pthread_detach() (что ваш объект Thread уже может делать), и вы стараетесь не разыменовывать this после удаления this, я думаю, что этот подход должен быть работоспособным, но другие предложения по использованию более долгоживущего потока (или пула потоков) заслуживают рассмотрения.

1 голос
/ 09 февраля 2010

Если все, что вы когда-либо делаете с Task объектом, это new это, start это, а затем delete это, зачем вам вообще нужен объект для него? Почему бы просто не реализовать функцию, которая делает то, что делает start (без создания и удаления объекта)?

...