次のコードがあるとします
#include <thread>
#include <iostream>
#include <atomic>
struct FooBase {
void start(){
run_condition_ = true;
t_ = std::thread([this](){
thread_handler();
});
}
virtual ~FooBase(){
run_condition_ = false;
if(t_.joinable())
t_.join();
}
protected:
virtual void thread_handler() = 0;
std::atomic_bool run_condition_{false};
private:
std::thread t_;
};
struct Foo : FooBase {
void thread_handler() override {
while(run_condition_){
std::cout << "Foo derived thread.." << std::endl;
}
}
};
int main(){
Foo f;
f.start();
getchar();
return 0;
}
ここで、派生クラスのデストラクタは、のデストラクタが実行されたときにスレッドがまだ参加していない(まだ実行されている)場合、基本クラスでvtableルックアップが発生するFoo
前FooBase
に呼び出されるためだと思います。以来純粋仮想である私は、基本的にSIGABORTをguranteedています。thread_handler
Foo
FooBase::thread_handler
これを防ぐにはどうすればよいですか?私thread_handler
は純粋な仮想を持っていないことによって私の道をハックします
virtual void thread_handler(){}
しかし、基本クラス自体でこれを防ぐ方法がわかりません。基本クラスにjoin_threadインターフェイスを実装し、すべての派生クラスからこれを呼び出すことができますが、これは面倒なようです。
ここには2つの問題がありますが、どちらもあなたが説明したものと正確には一致しません。
スレッドはでのみ停止し~FooBase()
ます。これはFoo::thread_handler
、そのメンバーのいずれかに対して読み取りまたは書き込みを行うと、スレッドが停止する前にそのメンバーの下から破棄されることを意味します。
デストラクタに十分な速さで到達した場合、破棄start()
されるthread_handler()
までに新しいスレッドで実際に呼び出されない可能性がありFoo
ます。これにより、純粋な仮想呼び出しが発生します。
いずれにせよ、Foo
破壊されるまでに、関連するすべてのことthread_handler
が行われるようにする必要があります。これFooBase
は、から派生したすべてのクラスが、そのデストラクタに次の要素を持たなければならないことを意味します。
run_condition_ = false;
if (t_.joinable()) {
t_join();
}
これが直接機能しないことt_
は別としてprivate
(これをにラップすることができますprotected
stop()
)、派生クラスのすべてが機能するためだけに特別なことを行う必要がある場合、これは厄介な設計です。代わりにFooBase
、引数として任意の呼び出し可能オブジェクトを受け取る独自のクラスに入れることができます。
class joining_thread {
public:
joining_thread() = default;
~joining_thread() { stop(); }
bool running() const { return run_condition_.load(); }
template <typename... Args>
void start(Args&&... args) {
run_condition_ = true;
t_ = std::thread(std::forward<Args>(args)...);
}
void stop() {
run_condition_ = false;
if (t_.joinable()) t_.join();
}
private:
std::thread t_;
std::atomic_bool run_condition_{false};
};
そして、あなたFoo
はそれをメンバーとして持つことができます:
class Foo {
public:
void start() {
t_.start([this]{
while (t_.running()) { ... }
});
}
private:
// just make me the last member, so it's destroyed first
joining_thread t_;
};
それはまだ全体的に少し厄介ですrunning()
が、うまくいけば、アイデアは理にかなっています。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加