2

スマート ポインターのデフォルトの初期化を指定するか、スマート ポインター メソッドにアクセスする前に値チェックを行う方がよいかを知りたいですか?NULL

increment()現在、NULLポインターの呼び出しを避けるために、以下のメソッドを使用しています。これは合理的な方法ですか、それとも私が知らない落とし穴がありますか?

注: カスタム スマート ポインター クラスを使用していますが、現在の構成には、このコードをテスト コンパイルするための Boost ライブラリがありません。これはコンパイルする必要がありますが、YMMV.

Example.h

#include <boost/shared_ptr.hpp>

class Foo
{
public:
  Foo() : mFoo(0) {}
  Foo(int rawValue) : mFoo(rawValue) {}

  void  increment() { mFoo++; }

private:
  int mFoo;
};

typedef boost::shared_ptr<Foo> FooSP;

class MyClass
{
public:
  MyClass() : mFoo(new Foo()) {}

  FooSP       foo() { return mFoo; }
  void        setFoo(FooSP newFoo) { mFoo = newFoo; }

private:
  FooSP mFoo;
};

メイン.cpp

#include <Example.h>

int main()
{
  MyClass temp;                    // Default-constructed
  temp.foo()->increment();         // Increment Foo's member integer
                                   // Before: mFoo = 0
                                   // After:  mFoo = 1
  FooSP tempFoo = new Foo(10);     // Create a Foo with a default size
  temp.setFoo(FooSP(new Foo(10))); // Explicitly set the FooSP member
  temp.foo()->increment();         // Increment the new FooSP
                                   // Before: mFoo = 10
                                   // After:  mFoo = 11
  return 0;
}
4

2 に答える 2

3

ポインター型の一般的な代替としてスマート ポインターを使用している場合、null のチェックから逃れることはできません。これは、既定のコンストラクターを持つスマート ポインターで定義されたクラスでは、既定のコンストラクターを使用してスマート ポインターを作成できる可能性が高いためです。ポインタを設定できるようになるまでポインタを埋めるためだけに新しいオブジェクトを動的に作成するのは、リソースの無駄のようです。

shared_ptrのコンストラクターは明示的であるため、の初期化はtempFooコンパイルされません。コード行を保存したい場合は、次のように一時的な宣言を避けることができます。

temp.setFoo(FooSP(new Foo(10)));

setFooパラメータを取り込むときに参照カウントを操作することを避けるために、定数参照を取るメソッドを宣言することもでき ます。

void setFoo(const FooSP &newFoo) { mFoo = newFoo; }

またはswap、パラメーター インスタンスで使用します。

void setFoo(FooSP newFoo) { std::swap(mFoo, newFoo); }

あなたが提案しているものに沿って何かを実装する必要がある場合はFoo、null バージョンとして機能する静的インスタンスを作成し、null バージョンのincrement場合はメソッドに例外をスローさせます。

class Foo
{
public:
  static Foo Null;
  //...
  void  increment() {
      if (this == &Null) throw Null;
      mFoo++;
  }
  //...
};

struct DeleteFoo {
    void operator () (Foo *t) const {
        if (t != &Foo::Null) delete t;
    }
};

class MyClass
{
public:
  MyClass() : mFoo(&Foo::Null, DeleteFoo()) {}
  //...
};

FooSPを適切に処理するためのカスタム デリータに注意してくださいFoo::Null

于 2012-06-14T17:48:50.480 に答える
2

スマート ポインターのデフォルトの初期化を指定するか、スマート ポインター メソッドにアクセスする前に NULL 値チェックを行う方がよいでしょうか?

すべてのケースに当てはまる正解はありません (近日中に公開予定)。どちらか一方に誤りを犯さなければならない場合NULL、デフォルトの初期化なしでテストする方向に誤ります。これは、簡単に検出して修正できる明らかなプログラマ エラーだからです。

ただし、構築と初期化に複数のイディオムを使用する正当な理由があり、プログラムに最適なアプローチを選択する必要があるというのが正しい答えだと思います。

通常、複雑な上位レベルのクラスだけでなく、下位レベルのクラスでも明示的 (デフォルトなしまたはデフォルトの初期化なし) になります。クラスが中間レベルであり、デフォルトと所有権がより明白な場合 (多くの場合、ユース ケースが限られているため)、デフォルトが賢明な場合があります。

多くの場合、クライアントを驚かせないために、一貫性を保つ必要があります。また、デフォルトで初期化されたオブジェクトの割り当ての複雑さに注意する必要があります。作成するのが大きくて複雑で、デフォルトが意味をなさない場合、デフォルトで構築されたオブジェクトが間違った選択であると、単に多くのリソースを無駄にしています。

  • a) 意味をなさないデフォルトを適用しないでください。デフォルトは明白なはずです。
  • b) 割り当ての無駄を避ける。

あなたが言及したアプローチに加えて、あなたが考慮できる他のいくつかの角度があります:

  • Fooで宣言されている のコンストラクターを照合しMyClassます。少なくとも、に関係するものMyClass
  • コピー可能で効率的にコピーできる場合は、のコンストラクターに aFooを渡します。MyClass
  • Fooコンテナー (この場合はスマート ポインター) をのコンストラクターに渡してあいまいさを取り除き、クライアントが望むようMyClassに構築 (共有ポインターの場合は共有) するオプションを提供します。Foo

これは合理的な方法ですか、それとも私が知らない落とし穴がありますか?

無駄な割り当て。驚くべき結果。機能を制限できます。最も明白で広く適用可能な問題は、時間とリソースの消費です。

いくつかのシナリオを説明するには:

  • Foo は、構築されるたびに 1MB のファイルを読み取るとします。コンストラクション パラメータが必要であり、デフォルトが適切なオプションではない場合、ファイルをもう一度読み込む必要があります。無害なデフォルトでは、必要なディスク IO が 2 倍になります。
  • 別のケースでは、省略された構築パラメーターが別の大きなまたは複雑な共有ポインターである可能性があります。存在しない場合、Foo は独自のものを作成する可能性があります -- リソースが共有されている可能性がある/共有されるべきである場合。

多くの場合、コンストラクターのパラメーターは非常に重要であり、多くの場合、インターフェイスから消去すべきではありません。場合によってはそうしても問題ないこともありますが、これらの便利さは、含まれるオブジェクトの複雑さが増すにつれて、多くの制限を導入したり、不要な割り当てや CPU 時間を導入したりする可能性があります。

プログラムで両方のアプローチを使用しても問題ありません。私が概説した追加のアプローチを使用することも問題ありません。具体的には、問題に対して適切なアプローチを使用することが理想的です。理想的なソリューションを実装する方法は複数あります。プログラムが何をしようとしているのかという文脈で、それが何であるかを判断するだけです。これらのアプローチにはそれぞれ長所と短所があります。多くの場合、プログラムの操作と公開されたインターフェイスのコンテキストには理想的な一致があります。

于 2012-06-14T18:02:20.807 に答える