Как позволить std :: thread автоматически удалять объект после исключения его функции-члена - PullRequest
2 голосов
/ 24 апреля 2019

Я хочу реализовать класс cmmand, который выполняет некоторую работу в другом потоке, и я не хочу позволять пользователям удалять этот объект вручную. Мой command класс выглядит так:

class Cmd {
 public:
  void excute() {
    std::cout << "thread begins" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));  // do some work
    std::cout << "thread ends" << std::endl;
  }

  void run() {
    // I want std::unique_ptr to delete 'this' after work is done,but does't work
    std::thread td(&Cmd::excute, std::unique_ptr<Cmd>(this));
    td.detach();
  }

  // test if this object is still alive
  void ok() { std::cout << "OK" << std::endl; }
};

Я использую это так:

int main() {
  Cmd *p = new Cmd();
  p->run();

  // waiting for cmd thread ends
  std::this_thread::sleep_for(std::chrono::seconds(3));

  p->ok();  // I thought p was deleted but not

  return 0;
}

Как и в комментариях, объект все еще жив после завершения потока cmd, я хочу знать, как реализовать такую ​​функциональность.

EDIT

пользователи cmd не знают, когда закончится cmd, поэтому текущий вариант использования приведет к UB.

std::unique_ptr<Cmd> up(new Cmd);  // or just Cmd c;
up->run();
// cmd will be deleted after out of scope but cmd::excute may still need it

ЗАКРЫТО

Я допустил ошибку в тесте, фактически объект удаляется после завершения потока. Это более понятно при следующем тесте с дополнительной переменной-членом int i.

#include <functional>
#include <iostream>
#include <stack>
#include <thread>

using namespace std;

class Cmd {
 public:
  ~Cmd() { std::cout << "destructor" << std::endl; }

  void excute() {
    std::cout << i << " thread begins" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));  // do some work
    std::cout << i << " thread ends" << std::endl;
  }

  void run() {
    // I want std::unique_ptr to delete 'this' after work is done,but it seems
    // not working
    std::thread td(&Cmd::excute, std::unique_ptr<Cmd>(this));
    td.detach();
  }

  // test if this object is still alive
  void ok() { std::cout << i << " OK" << std::endl; }

  int i;
};

int main() {
  Cmd *p = new Cmd();
  p->i = 10;
  p->run();

  // waiting for cmd thread ends
  std::this_thread::sleep_for(std::chrono::seconds(3));

  p->ok();  // I thought p was deleted but not

  return 0;
}

Вывод потока показал, что объект удален.

10 thread begins
10 thread ends
destructor
-572662307 OK

Но, как полагают некоторые добрые парни, это не очень хороший дизайн, избегайте его, как можете.

Ответы [ 2 ]

1 голос
/ 24 апреля 2019

Вместо потока вы можете использовать std :: future , чтобы сигнализировать о состоянии.Затем вы можете либо дождаться завершения задачи, либо полностью игнорировать будущее.

#include <future>
#include <chrono>
#include <mutex>
#include <iostream>

class Cmd {
public:
    std::future<void> run() {
        std::lock_guard<std::mutex> lock(startMutex);
        if (started) {
            throw std::logic_error("already started");
        }
        started = true;

        // Take copies here, so that it doesn't matter if Cmd is destroyed
        int i_ = i;
        return std::async(std::launch::async, [i_]() {
            std::this_thread::sleep_for(std::chrono::seconds(2));
            std::cout << i_ << std::endl;
        });
    }

    int i = 0;

private:
    std::mutex startMutex;
    bool started = false;
};

int main() {
    auto p = std::make_unique<Cmd>();
    p->i = 10;
    auto f = p->run();
    p.reset();

    // Do some other work

    // Wait for the task to finish (or use f.get() if there is no need to
    // do some other work while waiting)
    if (f.valid()) {
        std::future_status operation;
        do {
            // Do some other work

            operation = f.wait_for(std::chrono::milliseconds(1));
        } while (operation != std::future_status::ready);
    }
}
0 голосов
/ 24 апреля 2019

Существует два способа автоматического удаления памяти, созданной динамически.

  1. В клиентском коде (основная функция) вы должны использовать какой-нибудь умный указатель, такой как unique_pointer , чтобы после выхода объекта из области автоматически он былосвобожден в деструкторе unique_poiter

  2. Вы можете создать свой собственный умный указатель , это будет своего рода обертка вокруг класса Cmd.И должен перегружать несколько операторов.Этот умный указатель также будет заботиться о динамически распределенной памяти в деструкторе.Код клиента должен использовать этот умный указатель при динамическом создании объекта Cmd.

...