4

発生しているエラー(純粋仮想関数と呼ばれる)が発生している理由を理解しています。以下に示す基本クラスのデストラクタ内から純粋仮想関数を呼び出そうとしています。ただし、これを防ぐためにコードを作り直す方法がわかりません。基本クラスと派生クラス(とにかく関連する部分)は次のとおりです。

基本クラス:

TailFileManager::TailFileManager(const std::string &filename, const int fileOpenPeriod_ms)
: m_Stop(false)
{
    m_WorkerThread.reset(new boost::thread(boost::bind(&TailFileManager::TailFile, this, filename, fileOpenPeriod_ms)));
}

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

void TailFileManager::TailFile(const std::string &filename, const int fileOpenPeriod_ms)
{
    std::ifstream ifs(filename.c_str());

    while (! ifs.is_open())
    {
        boost::this_thread::sleep(boost::posix_time::milliseconds(fileOpenPeriod_ms));
    ifs.open(filename.c_str());
    }

    ifs.seekg(0, std::ios::end);

    while (! m_Stop)
    {
        ifs.clear();

        std::string line;

        while (std::getline(ifs, line))
        {
            OnLineAdded(line);
        }

        OnEndOfFile();
    }

    ifs.close();
}

派生クラス:

ETSLogTailFileManager::ETSLogTailFileManager(const std::string &filename, const int heartbeatPeriod_ms)
: TailFileManager(filename, heartbeatPeriod_ms),
  m_HeartbeatPeriod_ms(heartbeatPeriod_ms),
  m_FoundInboundMessage(false),
  m_TimeOfLastActivity(0)
{
}

ETSLogTailFileManager::~ETSLogTailFileManager()
{
}

void ETSLogTailFileManager::OnLineAdded(const std::string &line)
{
    // do stuff...
}

void ETSLogTailFileManager::OnEndOfFile()
{
    // do stuff...
}
4

4 に答える 4

12

構築中または破棄中に仮想関数を呼び出すべきではありません。呼び出しはあなたが思っていることを実行しないためです。呼び出しが実行されたとしても、それでも不幸になります。回復中のJavaまたはC#プログラマーの場合は、このアイテムに細心の注意を払ってください。これは、これらの言語がジグザグになり、C++がジグザグになる場所だからです。

デザインを作り直します。つまり、オブジェクトが破棄される前にクリーンアップ関数を呼び出すことができます。C++で作業している場合は、const / dest(存在する場合)中に仮想関数を回避することをお勧めします。

仮想呼び出しのルールは異なります。C ++ 2003、セクション12.7「構築と破壊」は次のように述べています。

古い思い出をリフレッシュしましょう...

仮想関数(10.3)を含むメンバー関数は、構築中または破棄中に呼び出すことができます(12.6.2)。仮想関数がコンストラクター(データメンバーのmem-initializerを含む)またはデストラクタから直接または間接的に呼び出され、呼び出しが適用されるオブジェクトが構築中または破棄中のオブジェクトである場合、呼び出される関数はコンストラクタまたはデストラクタ自身のクラスまたはそのベースの1つで定義されているが、コンストラクタまたはデストラクタのクラスから派生したクラスでオーバーライドしたり、最も派生したオブジェクトの他の基本クラスの1つでオーバーライドしたりする関数ではない(1.8 )。仮想関数呼び出しが明示的なクラスメンバーアクセスを使用する場合(5.2。

この動作の違いのため、オブジェクトの構築中または破棄中は、オブジェクトの仮想関数を呼び出さないことをお勧めします。

構築中または破壊中に仮想関数を呼び出さないでください。ScottMeyersによる効果的なC++、第3版からの抜粋 2005年6月6日

http://www.artima.com/cppsource/nevercall.html

于 2013-01-27T16:53:42.427 に答える
7

C ++標準に関する限り、次のようになります。

  • コンストラクタまたはデストラクタで仮想関数を呼び出すと、その動的タイプが実行中の現在のコンストラクタ/デストラクタの動的タイプであるかのように、関数が動的にディスパッチされます(§12.7/ 4)
  • その関数が純粋な仮想に起こった場合、これは未定義の動作です(§10.4/ 6)。Itanium ABIは動作を定義します:__cxa_pure_virtualと呼ばれます。

だから、あなたは少し厄介な問題を抱えています...


この問題の考えられる解決策は、破壊を2つの部分に分割するために、2つの部分に分割することです。Strategyこれは、次のパターンで実現できます。

  • カスタマイズ可能なインターフェース、戦略を提供する
  • 機能をカプセル化し、カスタマイズ可能な部分の戦略に従うマネージャークラスを提供します

明確にしましょう:

class Interface {
public:
    friend class Manager;

private:
    virtual void finalize() = 0;
}; // class Interface


class Manager {
public:
    explicit Manager(std::unique_ptr<Interface>&&);

    ~Manager();

private:
    std::unique_ptr<Interface> _interface;
}; // class Manager

Manager::~Manager() {
    _interface->finalize();
}

トリック ?と呼ばれる時点でfinalize()の破壊は_interfaceまだ始まっていません!デストラクタへの呼び出しは後で行われます。したがって、あなたは半死の物体の運命に苦しむことはありません。

joinこの回答は、デストラクタでスレッドを実行することについての警告で終了します。スタックが巻き戻されると、デストラクタが自動的に呼び出されることに注意してください。したがって、失敗している間、無期限に待機するのは危険な場合があります。特に、スレッドが現在巻き戻されているデータによって提供されるデータを待機している場合...デッドロックの典型的なケース。


参照(n3337):

§12.7/4仮想関数(10.3)を含むメンバー関数は、構築中または破棄中(12.6.2)に呼び出すことができます。クラスの非静的データメンバーの構築中または破棄中を含め、コンストラクタまたはデストラクタから仮想関数が直接または間接的に呼び出され、呼び出しが適用されるオブジェクトが構築中のオブジェクト(xと呼ぶ)である場合または破棄の場合、呼び出される関数はコンストラクタまたはデストラクタのクラスの最後のオーバーライドであり、より派生したクラスでオーバーライドする関数ではありません

§10.4/6メンバー関数は、抽象クラスのコンストラクタ(またはデストラクタ)から呼び出すことができます。このようなコンストラクタ(またはデストラクタ)から作成(または破棄)されているオブジェクトに対して、純粋仮想関数を直接または間接的に仮想呼び出し(10.3)する効果は定義されていません。

于 2013-01-27T18:47:38.907 に答える
5

あなたが書く、

「以下に示す基本クラスのデストラクタ内から純粋仮想関数を呼び出そうとしています。」

そして問題のコードは

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

幸いなことに、シングルスレッドの実行では、これは純粋仮想関数を呼び出すことができなかった可能性があります。ただし、使用しているスレッドはjoin、おそらく非仮想メンバー関数を介して、このオブジェクトで純粋仮想関数を呼び出す可能性があります。もしそうなら、問題はスレッド化、特にこのオブジェクトのライフタイム管理にあります。

残念ながら、関連するコードは表示されません。物事を小さく、完全で、実用的な例に減らしてみてください。問題を再現するという意味で「機能している」場合。

于 2013-01-27T17:26:40.213 に答える
0

使用しているシステムによっては、これが機能する場合があります。

純粋関数呼び出しハンドラーにブレークポイントを設定します__cxa_pure_virtual

これはgdbを使用して私のために働きました:break __cxa_pure_virtualそして、ブレークポイントがヒットされたとき、upバストされた私のコードを示しました。(私の場合、2番目のスレッドを導入していますが、オブジェクトが使用されている間、現在、別のスレッドで破棄が発生していました。)

ドキュメント__cxa_pure_virtualhttps ://www.swag.uwaterloo.ca/acd/docs/ItaniumC++ABI.htm#pure-virtual

于 2021-10-27T10:45:28.417 に答える