5

Javaでスレッドを少し使用した後、スレッドを理解しようとしていますが、少し困惑しています。2 つの質問:

  • スレッドからクラスを拡張できますか? またはハンドラーを介してクラス内からスレッドを管理する必要がありますか?
  • 上記のスレッドハンドラーを保存するにはどうすればよいですか? std::thread 自体は、型に名前を付けているようには見えません。

正しい方向への製品は大歓迎です。

このメッセージをどのように解釈すればよいですか?

src/CHandler.h:27:9: error: 'thread' in namespace 'std' does not name a type
         std::thread _thread;
         ^

そして、スレッドを拡張する私の試みは次のとおりです。

src/CHandler.h:17:30: error: expected class-name before '{' token
 class CHandler : std::thread {
                              ^

完全で厄介なヘッダー:

#ifndef __projectm__CHandler__
#define __projectm__CHandler__

#include <set>
#include <vector>
#include <thread>

#include "CListener.h"

class CHandler {
    public:
        virtual bool subscribe(std::shared_ptr<CListener> aListener);
        virtual bool unsubscribe(std::shared_ptr<CListener> aListener);

        virtual bool hasSubscriber(std::shared_ptr<CListener> aListener);

        virtual ~CHandler() {}

    protected:
        std::thread _thread;
        std::vector<std::weak_ptr<CListener> > _subscribers;
        std::set<const CListener *> _subscribersSet;

        virtual void run();
};

#endif /* defined(__projectm__CDefaultHandler__) */

コンパイラのバージョン:

bash-3.1$ g++ --version
g++.exe (GCC) 4.8.1

makefile (混乱、私は知っています - この血なまぐさいことをまだ学んでいます):

CC=g++

OUTFILE=game

BINDIR=bin
SRCDIR=src
OBJDIR=obj

CFLAGS=
LDFLAGS=-std=c++0x



all: core

# Ядро проекта.
core: $(OBJDIR)/main.o $(OBJDIR)/CGame.o $(OBJDIR)/CHandler.o $(OBJDIR)/CListener.o
    $(CC) $(CFLAGS) $(wildcard $(OBJDIR)/*.o) -o $(BINDIR)/$(OUTFILE)

$(OBJDIR)/main.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/main.cpp -c -o $(OBJDIR)/main.o

$(OBJDIR)/CGame.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CGame.cpp -c -o $(OBJDIR)/CGame.o

$(OBJDIR)/CHandler.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CHandler.cpp -c -o $(OBJDIR)/CHandler.o

$(OBJDIR)/CListener.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CListener.cpp -c -o $(OBJDIR)/CListener.o

# Создаем директорию для объектов, если ее нет.
$(OBJDIR):
    mkdir $(OBJDIR)

main.o: $(SRC)/main.cpp
4

5 に答える 5

10

飾り気のないローカル変数として使用する際の問題の 1 つは、std::thread例外セーフではないことです。小さな小さな HelloWorld をデモンストレーションするとき、私自身がしばしばこの罪を犯していることを認めます。

ただし、何をしようとしているのかを正確に知ることは良いことなので、 を使用する際の例外の安全性の側面について、より詳細な説明を以下に示しstd::threadます。

#include <iostream>
#include <thread>

void f() {}
void g() {throw 1;}

int
main()
{
    try
    {
        std::thread t1{f};
        g();
        t1.join();
    }
    catch (...)
    {
        std::cout << "unexpected exception caught\n";
    }
}

上記の例では、「時折」例外をスローする「大規模な」プログラムがあります。通常、例外が にバブルアップする前に例外をキャッチして処理したいと考えていmainます。ただし、最後の手段として、mainそれ自体が try-catch-all に包まれています。この例では、本当に悪いことが起こったことを出力して終了します。より現実的な例では、クライアントに作業を保存する機会を与えたり、メモリやディスク領域を解放したり、バグ レポートを提出する別のプロセスを起動したりすることができます。

良さそうですよね?残念ながら間違っています。これを実行すると、出力は次のようになります。

libc++abi.dylib: terminating
Abort trap: 6

main通常から戻る前に、何か問題が発生したことをクライアントに通知しませんでした。私はこの出力を期待していました:

unexpected exception caught

代わりstd::terminate()に呼ばれました。

なんで?

結局のところ、~thread()次のようになります。

thread::~thread()
{
    if (joinable())
        terminate();
}

そのため、g()スローすると、t1.~thread()スタックの巻き戻し中に実行され、t1.join()呼び出されることはありません。したがってt1.~thread()、 を呼び出しますstd::terminate()

理由を聞かないでください。それは長い話であり、公平に語るには客観性に欠けます。

いずれにせよ、この動作について知っておく必要があり、それを防ぐ必要があります。

考えられる解決策の1つは、おそらくOPによって最初に提案され、他の回答で警告されたプライベート継承を使用して、ラッパー設計に戻ることです。

class CHandler
    : private std::thread
{
public:
    using std::thread::thread;
    CHandler() = default;
    CHandler(CHandler&&) = default;
    CHandler& operator=(CHandler&&) = default;
    ~CHandler()
    {
        if (joinable())
            join();  // or detach() if you prefer
    }
    CHandler(std::thread t) : std::thread(std::move(t)) {}

    using std::thread::join;
    using std::thread::detach;
    using std::thread::joinable;
    using std::thread::get_id;
    using std::thread::hardware_concurrency;

    void swap(CHandler& x) {std::thread::swap(x);}
};

inline void swap(CHandler& x, CHandler& y) {x.swap(y);}

その意図は、新しいタイプを作成することです。たとえば、デストラクタを除いて、 と CHandler同じように動作します。デストラクタでまたはを呼び出す必要があります。以上を選択しました。これで、サンプル コードで次のように置き換えることができます。std::thread~CHandler()join()detach()join()CHandlerstd::thread

int
main()
{
    try
    {
        CHandler t1{f};
        g();
        t1.join();
    }
    catch (...)
    {
        std::cout << "unexpected exception caught\n";
    }
}

出力は次のようになります。

unexpected exception caught

意図した通り。

join()の代わりにdetach()選択~CHandler()する理由

を使用するjoin()と、メイン スレッドのスタックの巻き戻しがf()完了するまでブロックされます。これはあなたが望むものかもしれませんし、そうでないかもしれません。この質問にはお答えできません。アプリケーションのこの設計上の問題を決定できるのは、あなただけです。検討:

// simulate a long running thread
void f() {std::this_thread::sleep_for(std::chrono::minutes(10));}

スレッドは依然としてのmain()下で例外をスローしますがg()、巻き戻し中にハングし、わずか 10 分後に次のように出力されます。

unexpected exception caught

そして終了します。おそらく、 内で使用される参照またはリソースが原因でf()、これが発生する必要があります。ただし、そうでない場合は、代わりに次のことができます。

    ~CHandler()
    {
        if (joinable())
            detach();
    }

その後、プログラムはすぐに「予期しない例外がキャッチされました」を出力して戻りますf()が、まだ処理中です(アプリケーションの通常のシャットダウンの一部としてmain()戻りf()が強制的にキャンセルされた後)。

join()-on-unwindingおそらく、一部のスレッドと他のスレッドに必要ですdetach()-on-unwindingCHandlerおそらく、これは 2 つのようなラッパー、またはポリシーベースのラッパーにつながります。委員会は解決策についてコンセンサスを形成することができませんでした。そのため、あなたは自分にとって何が正しいかを決定するか、または と一緒に暮らす必要がありますterminate()

std::threadこれにより、非常に低レベルの動作が直接使用されます。Hello World は OK ですが、実際のアプリケーションでは、プライベート継承またはプライベート データ メンバーとして中間レベルのハンドラーにカプセル化するのが最適です。std::thread幸いなことに、C++11では、C++98/03 で必要な OS またはサードパーティのライブラリに書き込む代わりに、中間レベルのハンドラーを (上に) 移植可能に記述できるようになりました。

于 2013-10-17T17:11:48.597 に答える
0

コンパイルしてリンクするときは、次のものを使用してください。

g++ -std=c++11 your_file.cpp -o your_program 

LDFLAGS をいじっても、コンパイルではなく、リンクにのみ役立ちます。

于 2013-10-17T09:35:04.257 に答える
0

まず、使用しているコンパイラとコンパイラのバージョンは何ですか? std::thread はかなり新しく、ごく最近まで実装されていなかったものもあります。それはあなたの問題かもしれません。

第二に、あなたは

#include <thread> 

第 3 に (これは差し迫った問題ではありません)、C++ でスレッドを使用する方法ではありません。それを継承するのではなく、実行したい関数を渡すインスタンスを作成します。

std::thread mythread = std::thread(my_func);

(ただし、単純な関数よりも多くを渡すことができます)

于 2013-10-17T09:28:08.117 に答える