10

編集:解決済み

私は現在、マルチスレッドプロジェクトに取り組んでおり、ベースワーカークラスがあり、そこから継承するさまざまなワーカークラスがあります。実行時に、ワーカークラスはスレッドになり、スレッドは必要に応じて作業を実行します。

これで、すべてのワーカーへのポインターの配列を維持することになっているディレクターが作成されました。これにより、ワーカーから情報を取得したり、後でワーカー内の変数を変更したりできます。

これを行うには、基本クラスのポインターへのポインターを作成します。

baseWorkerClass** workerPtrArray;

次に、Directorのコンストラクターで、ポインターの配列を基本ワーカークラスに動的に割り当てます。

workerPtrArray = new baseWorkerClass*[numWorkers];

各ワーカースレッドのコンストラクターで、ワーカーはディレクター内の関数を呼び出します。この関数は、そのワーカーのポインターを配列に格納することを目的としています。

ディレクターがポインターを格納する方法は次のとおりです。

Director::manageWorker(baseWorkerClass* worker)
{
    workerPtrArray[worker->getThreadID()] = worker;
}

ワーカーバリアントの例を次に示します。各ワーカーは基本ワーカークラスから継承し、基本ワーカークラスには、すべてのワーカーバリアントに存在する必要がある純粋仮想関数と、すべてのワーカー間で共有されるいくつかの変数が含まれます。

class workerVariant : protected baseWorkerClass
{
    public:

    workerVariant(int id)
    : id(id)
    {
        Director::manageWorker(this);
    }

    ~workerVariant()
    {
    }

    int getThreadID()
    {
        return id;
    }

    int getSomeVariable()
    {
        return someVariable;
    }

    protected:

    int id;
    int someVariable
};

次に、baseWorkerClassは次のようになります。

class baseWorkerClass
{
public:

    baseWorkerClass()
    {
    }

    ~baseWorkerClass()
    {
    }

    virtual int getThreadID() = 0;
    virtual int getSomeVariable() = 0;
};

各ワーカーバリアントの初期化が完了すると、baseWorkerClassオブジェクトへのポインターの配列ができあがります。これは、たとえば、次のように、IDを配列のインデックスとして使用して、特定のワーカーの特定の変数の値を取得できる必要があることを意味します。

workerPtrArray[5]->getSomeVariable(); // Get someVariable from worker thread 5

問題は、このコードが理由の説明なしにWindows実行可能ファイルでクラッシュを引き起こすことであり、Linuxでは次のように述べています。


アクティブな例外なしで呼び出されたterminateと呼ばれる純粋仮想メソッドが
中止 されました

ある時点でこれが機能していると誓うことができたので、何を台無しにしたのか混乱しています。


問題のある実際の変更されていないコード:

ワーカーバリアントヘッダー: http: //pastebin.com/f4bb055c8
ワーカーバリアントソースファイル:http://pastebin.com/f25c9e9e3

ベースワーカークラスヘッダー: http: //pastebin.com/f2effac5
ベースワーカークラスソースファイル:http://pastebin.com/f3506095b

ディレクターヘッダー: http: //pastebin.com/f6ab1767a
ディレクターソースファイル:http://pastebin.com/f5f460aae


編集:追加情報、manageWorker関数では、ポインター「worker」から純粋仮想関数のいずれかを呼び出すことができ、それは問題なく機能します。manageWorker関数の外で、ポインター配列を使おうとすると失敗します。

編集:今考えてみると、スレッドのエントリポイントはoperator()です。Directorスレッドはワーカーの前に作成されます。これは、オーバーロードされた括弧演算子が、子クラスによってオーバーライドされる前に純粋仮想関数を呼び出していることを意味する場合があります。私はこれを調べています。

4

7 に答える 7

12

問題は、インスタンスDirector::manageWorkerのコンストラクターで呼び出されることのようです。workerVariant

Director::manageWorker(baseWorkerClass* worker) {
    workerPtrArray[worker->getThreadID()] = worker;
}

おそらくgetThreadID()、純粋仮想関数ではないか、(うまくいけば!)でオーバーライドしないことについてコンパイラエラーが発生するでしょうworkerVariant。ただしgetThreadID()、オーバーライドする必要があるが、抽象クラスで呼び出されている他の関数を呼び出す場合があります。の定義を再確認getThreadID()して、子クラスが適切に初期化される前に、子クラスに依存するような不利なことをしていないことを確認する必要があります。

より良い解決策は、この種の多段階初期化を別のメソッドに分離するか、この種の初期化と時間の相互依存性を持たないようDirectorに設計することです。baseWorkerClass

于 2010-01-30T09:03:28.227 に答える
3

完全なコードを確認しないと、 が指すメモリ ブロックの境界から出ているのではないかと推測する危険がありますworkerPtrArray。純粋な仮想関数が呼び出されていることに文句を言うので、それは確かに理にかなっています。逆参照されているメモリがガベージである場合、ランタイムはそれをまったく理解できず、奇妙なことが起こります。

配列を逆参照している重要な場所にアサートを配置して、インデックスが意味を成していることを確認してください。つまり、ワーカーを 4 つに制限し、id が 4 未満であることを確認します。

于 2010-01-30T11:12:51.180 に答える
2

初期化中、クラスは部分的にのみ構築されます。具体的には、各派生クラスのコンストラクターがその基本メンバーに安全にアクセスできるように、コンストラクターは最上位のクラスから実行する必要があります。

これは、部分的に構築されたクラスの vtable を最終状態にすることができないことを意味します。仮想メソッドが派生クラスを呼び出すことが許可されている場合、それらのメソッドは、そのクラス コンストラクターが呼び出される前に呼び出されます。

つまり、構築中の純粋仮想関数は、実際には純粋仮想です。最新の C++ コンパイラはこれをキャッチする能力が向上していますが、多くの場合、コンパイラがエラーに気付かないように不正な呼び出しを「埋めて」しまう可能性があります。

話の教訓: 仮想関数を呼び出すコンストラクターで何もしないでください。それはあなたが期待することをしません。たとえそれが純粋でなくても。

于 2010-01-30T11:49:46.970 に答える
2

呼び出される純粋仮想関数は、基底クラスで純粋なメンバーが、子孫のコンストラクターの実行が開始される前に呼び出されることを意味します。非マルチスレッド プログラムでは、これは基本クラスのコンストラクターで直接または間接的に行われることを意味します。マルチスレッド プログラムでは、コンストラクターがコンストラクター内でスレッドを起動し、システムがコンストラクターを終了する前にスレッドを実行する場合にも、これが発生する可能性があります。

于 2010-01-30T10:54:53.727 に答える
1

どのコードサンプルでも、バリアントクラスが構築されているのを見ませんでした。渡されるIDがワーカー配列の範囲内にあることを確認しますか?また、「new」を使用してオブジェクトを構築していますよね?スタック上にオブジェクトを構築した場合、それ自体がDirectorに登録されますが、コンストラクターが戻った後、オブジェクトはすぐに破棄されますが、Directorはスタック上にあったオブジェクトへのポインターを保持します。

また、baseWorkerClassの配列を削除したときに確実に呼び出されるように、baseWorkerClassデストラクタはworkerVariantデストラクタとともに仮想である必要があります。

私のコメントから別の質問まで、ダブルポインタの代わりにstd::vectorを使用することを検討してください。保守と理解が容易になり、アレイを保守する必要がなくなります。

ここに不要な抽象化レイヤーを追加したようです。IDは実際にはサブクラスインターフェイスの一部である必要はないと思います。私はこのようなものがあなたにとってよりうまくいくかもしれないと思います:

class baseWorkerClass
{
public:

    baseWorkerClass(int id) :
        id( id )
    {
    }

    virtual ~baseWorkerClass()
    {
    }

    int getThreadID(){ return id; };
    virtual int getSomeVariable() = 0;

protected:
    int id;
};

class workerVariant : protected baseWorkerClass
{
    public:

    workerVariant(int id) :
        baseWorkerClass( id )
    {
        Director::manageWorker(this);
    }

    virtual ~workerVariant()
    {
    }

    int getSomeVariable()
    {
        return someVariable;
    }

protected:
    int someVariable
};
于 2010-01-30T21:23:56.000 に答える
0

私はこのエラーメッセージを一度受け取りました。それは質問者の正確なケースとは関係ありませんが、他の人に役立つことを期待してこれを追加します:

クリーンビルドを行うことで問題を修正しました。

于 2011-07-15T03:46:36.690 に答える