3

メモリ リークをチェックした後、アプリケーションでこの問題に遭遇し、一部のクラスがまったく破棄されていないことを発見しました。

以下のコードは 3 つのファイルに分割されており、pimplというパターンを実装することになっています。予想されるシナリオは、Cimplコンストラクタとデストラクタの両方にメッセージを出力させることです。ただし、それはg ++で得られるものではありません。私のアプリケーションでは、コンストラクターのみが呼び出されました。

クラス.h:

#include <memory>

class Cimpl;

class Cpimpl {
    std::auto_ptr<Cimpl> impl;
public:
    Cpimpl();
};

クラス.cpp:

#include "classes.h"
#include <stdio.h>

class Cimpl {
public:
    Cimpl() {
        printf("Cimpl::Cimpl()\n");
    }
    ~Cimpl() {
        printf("Cimpl::~Cimpl()\n");
    }
};    

Cpimpl::Cpimpl() {
    this->impl.reset(new Cimpl);
}

main.cpp:

#include "classes.h"

int main() {
    Cpimpl c;
    return 0;
}

さらに発見できたのは次のとおりです。

g++ -Wall -c main.cpp
g++ -Wall -c classes.cpp
g++ -Wall main.o classes.o -o app_bug
g++ -Wall classes.o main.o -o app_ok

デストラクタが 2 つの考えられるケースのいずれかで呼び出されているように見えますが、これはリンクの順序によって異なります。app_ok を使用すると、正しいシナリオを取得できましたが、app_bug はアプリケーションとまったく同じように動作しました。

この状況で私が見逃している知恵はありますか?事前にご提案いただきありがとうございます。

4

4 に答える 4

1

auto_ptr<Cimpl>問題は、オブジェクトの定義の時点で が Cimpl不完全な型であることです。つまり、コンパイラは の前方宣言しか認識していませんCimpl。それは問題ありませんが、最終的にはポインターを保持しているオブジェクトを削除するため、[expr.delete]/5 から、この要件に準拠する必要があります。

削除されるオブジェクトが削除の時点で不完全なクラス型を持ち、完全なクラスに重要なデストラクタまたは割り当て解除関数がある場合、動作は未定義です。

したがって、このコードは未定義の動作を実行し、すべての賭けが無効になります。

于 2012-09-07T18:52:09.387 に答える
1

pimpl イディオムの目的は、ヘッダー ファイルで実装クラスの定義を公開する必要がないようにすることです。ただし、標準のスマート ポインターはすべて、正しく機能するために、宣言の時点でテンプレート パラメーターの定義を可視にする必要があります。

newつまり、これは、 、delete、およびベア ポインターを実際に使用するまれなケースの 1 つです。(これについて私が間違っていて、にきびに使用できる標準のスマートポインターがある場合は、誰かが私に知らせてください。)

クラス.h

struct Cimpl;

struct Cpimpl
{
    Cpimpl();
    ~Cpimpl();

    // other public methods here

private:
    Cimpl *ptr;

    // Cpimpl must be uncopyable or else make these copy the Cimpl
    Cpimpl(const Cpimpl&);
    Cpimpl& operator=(const Cpimpl&);
};

クラス.cpp

#include <stdio.h>

struct Cimpl
{
    Cimpl()
    {
        puts("Cimpl::Cimpl()");
    }
    ~Cimpl()
    {
        puts("Cimpl::~Cimpl()");
    }

    // etc
};

Cpimpl::Cpimpl() : ptr(new Cimpl) {}
Cpimpl::~Cpimpl() { delete ptr; }

// etc
于 2012-09-07T18:46:23.753 に答える
0

コードは 1 つの定義規則に違反しています。classes.h にはクラスの定義があり、ファイル classes.cpp にCimplはクラスの別の定義があります。Cimplその結果、未定義の動作になります。クラスの定義が複数あってもかまいませんが、それらは同じでなければなりません。

于 2012-09-07T18:14:27.987 に答える
0

明確にするために編集され、元は以下に保持されます。

main.cppこのコードの動作は未定義です。これは、暗黙のデストラクタのコンテキストでCpimpl::~Cpimplは の前方宣言しかないためですCimpl。ただし、auto_ptr(またはその他の形式の実行delete) では、 を合法的にクリーンアップするために完全な定義が必要Cimplです。それが未定義の動作であることを考えると、あなたの観察に対するこれ以上の説明は必要ありません。

元の答え:

ここで起こっていることは、 の暗黙的なデストラクタがCpimplのコンテキストで生成され、の完全な定義にアクセスできないclasses.hことだと思います。次に、 が処理を実行して含まれているポインターをクリーンアップしようとすると、未定義の動作である不完全なクラスが削除されます。定義されていないことを考えると、リンクの順序に応じてさまざまな方法で動作することは完全に許容されることを説明するためにこれ以上説明する必要はありません。Cimplauto_ptr

ソースファイルに定義された明示的なデストラクタがCpimpl問題を解決すると思います。

編集:実際にもう一度見てみると、あなたのプログラムは現状の1つの定義規則に違反していると思います。main.cppのデストラクタを呼び出す方法がわからない暗黙のデストラクタが表示されます(Cimpl前方宣言しかないため)。classes.cpp暗黙のデストラクタでは、の定義にアクセスできるため、そのデストラクタを呼び出す方法があります。Cimpl

于 2012-09-07T18:14:48.000 に答える