質問

pImplイディオムを使用する場合は、boost:shared_ptr std::auto_ptr?ブーストバージョンの方が例外に優しいことを一度読んだことがありますか?

class Foo
{
public:
    Foo();
private:
    struct impl;
    std::auto_ptr<impl> impl_;
};

class Foo
{
public:
    Foo();
private:
    struct impl;
    boost::shared_ptr<impl> impl_;
};

[EDIT] std :: auto_ptr <!> lt; <!> gt;を使用することは常に安全ですか?または、代替のブーストスマートポインターが必要な状況がありますか?

役に立ちましたか?

解決

実際にはstd :: auto_ptrを使用しないでください。デストラクタは、std :: auto_ptrを宣言した時点では表示されないため、適切に呼び出されない可能性があります。これは、pImplクラスを前方宣言し、別のファイルのコンストラクター内にインスタンスを作成することを前提としています。

boost :: scoped_ptr (ここにはshared_ptrは必要ありません。他のオブジェクトと共有することはありません。これはscoped_ptrが noncopyable )、scoped_ptrコンストラクターを呼び出した時点で表示可能なpimplデストラクタのみが必要です。

E.g。

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

ここで、コンパイラはMyClassのデストラクタを生成します。 auto_ptrのデストラクタを呼び出す必要があります。 auto_ptrデストラクタがインスタンス化される時点で、Pimmplは不完全な型です。そのため、Pimplオブジェクトを削除するときのauto_ptrデストラクタでは、Pimplデストラクタを呼び出す方法がわかりません。

boost :: scoped_ptr(およびshared_ptr)にはこの問題はありません。scoped_ptr(またはresetメソッド)のコンストラクターを呼び出すと、deleteを呼び出す代わりに使用するfunction-pointer-equivalentも作成されるためです。 。ここで重要なのは、Pimmplが不完全な型でない場合に割り当て解除関数をインスタンス化することです。補足説明として、shared_ptrでは、カスタム割り当て解除関数なので、GDIハンドルなど、必要なものに使用できますが、それはここでのニーズには行き過ぎです。

std :: auto_ptrを本当に使用する場合は、Pimmplが完全に定義されているときにMyClass.cppでMyClassデストラクタを定義するように注意する必要があります。

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
    ~MyClass();
};

and

// in MyClass.cpp

#include "Pimpl.h"

MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}

MyClass::~MyClass() 
{
    // this needs to be here, even when empty
}

コンパイラは、空のデストラクタ内ですべてのMyClassメンバーを効果的に「破壊」するコードを生成します。そのため、auto_ptrデストラクタがインスタンス化された時点で、Pimmplは不完全ではなくなり、コンパイラはデストラクタを呼び出す方法を認識します。

個人的に、すべてが正しいことを確認する手間をかける価値があるとは思いません。また、誰かが後でやって来て、一見冗長なデストラクタを削除することでコードを整理するというリスクもあります。そのため、この種の場合はboost :: scoped_ptrを使用する方が安全です。

他のヒント

auto_ptrを使用する傾向があります。必ずクラスをコピー不可にしてください(private copy ctor <!> amp; operator =を宣言するか、boost::noncopyableを継承します)。 implを使用すると、ボディが空であっても、非インラインデストラクタを定義する必要があるという欠点があります。 (これは、コンパイラにデフォルトのデストラクタを生成させると、delete impl_の呼び出しが生成されたときに未定義の動作を呼び出して、<=>が不完全な型になるためです)。

<=> <!> amp;から選択するものはほとんどありません。ブーストポインター。標準的なライブラリの代替手段があれば、スタイル上の理由でブーストを使用しない傾向があります。

std::auto_ptrのブースト代替はboost::scoped_ptrです。 auto_ptrとの主な違いは、<=>はコピー不可であることです。

詳細については、このページをご覧ください。

boost :: shared_ptrは、pimplイディオム用に特別に調整されています。主な利点の1つは、pimplを保持するクラスのデストラクタを定義しないことです。共有所有権ポリシーには、長所と短所があります。ただし、後の場合、コピーコンストラクタを適切に定義できます。

本当に慢な場合は、auto_ptrメンバーを使用しても、const auto_ptrのテンプレートパラメーターを使用する時点で完全に定義する必要がないという絶対的な保証はありません。とはいえ、これが機能しないことは一度もありません。

1つのバリエーションは、<=>を使用することです。これは、初期化子リスト内に新しい式を使用して「pimpl」を構築でき、コンパイラがデフォルトのコピーコンストラクタと割り当てメソッドを生成できないことを保証する限り機能します。外側のクラスの非インラインデストラクタを提供する必要があります。

他の条件が同じであれば、物事の移植性を保つため、標準ライブラリのみを使用する実装を優先します。

コピー可能なクラスが必要な場合は、コピーを禁止するscoped_ptrを使用します。これにより、デフォルトでクラスを誤って使用しにくくします(shared_ptrを使用する場合と比較して、コンパイラは単独でコピー機能を出力しません)。 <=>の場合、自分が何をするのかわからない場合(ウィザードでも十分な場合が多い)、突然何かのコピーがその何かを変更した場合、奇妙な動作が発生します)コピーコンストラクターとコピー割り当て:

class CopyableFoo {
public:
    ...
    CopyableFoo (const CopyableFoo&);
    CopyableFoo& operator= (const CopyableFoo&);
private:
    scoped_ptr<Impl> impl_;
};

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}

shared_ptrは、pImplのauto_ptrよりもはるかに望ましい方法です。コピーすると、外部クラスが突然ポインターを失う可能性があるためです。

shared_ptrを使用すると、前方宣言された型を使用して動作させることができます。 auto_ptrは、前方宣言された型を許可しません。 scoped_ptrも同様です。外部クラスがとにかくコピー不可になり、ポインターが1つしかない場合は、通常のポインターでもかまいません。

pImplで侵入参照カウントを使用し、その実装で外部クラスにそのコピーを呼び出してセマンティクスを割り当てるようにすることについて、多くのことが言われています。これが真のベンダー(クラスを提供する)モデルであると仮定すると、ベンダーがユーザーにshared_ptrの使用や、同じバージョンのshared_ptr(boostまたはstd)の使用を強制しないことが望ましいです。

Vladimir Batovによるimpl_ptr [変更] 。明示的なコピーコンストラクターと代入演算子を作成する必要なく、pImplを簡単に作成できます。

元のコードを変更したので、shared_ptrに似ているため、エピローグコードで使用でき、高速のままです。

C ++では、たくさんの機会があります。 オブジェクトが(コンストラクターとデストラクター内で)いつ出入りするかを完全に知っているので、自動ポインターを使用する必要はありません。

シンプルにしてください。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top