17

これは、標準 C++ で Final のような動作を実装する良い方法ですか? (特別なポインタなし)

class Exception : public Exception
    { public: virtual bool isException() { return true; } };

class NoException : public Exception
    { public: bool isException() { return false; } };


Object *myObject = 0;

try
{
  // OBJECT CREATION AND PROCESSING
  try
  {
    myObject = new Object();

    // Do something with myObject.
  }

  // EXCEPTION HANDLING
  catch (Exception &e)
  {
    // When there is an excepion, handle or throw,
    // else NoException will be thrown.
  }

  throw NoException();
}

// CLEAN UP
catch (Exception &e)
{
  delete myObject;

  if (e.isException()) throw e;
}
  1. オブジェクトによって例外がスローされない -> NoException -> オブジェクトがクリーンアップされる
  2. オブジェクトによってスローされた例外 -> 処理済み -> NoException -> オブジェクトのクリーンアップ
  3. オブジェクトによってスローされた例外 -> スローされた -> 例外 -> オブジェクトがクリーンアップされた -> スローされた
4

6 に答える 6

34

標準的な答えは、resource-allocation-is-initialization の省略形 RAII のバリアントを使用することです。基本的に、finally の前にブロック内にあるブロックと同じスコープを持つ変数を作成し、オブジェクト デストラクタ内の finally ブロックで作業を行います。

try {
   // Some work
}
finally {
   // Cleanup code
}

になる

class Cleanup
{
public:
    ~Cleanup()
    {
        // Cleanup code
    }
}

Cleanup cleanupObj;

// Some work.

これは非常に不便に見えますが、通常、クリーンアップを行う既存のオブジェクトがあります。あなたの場合、finally ブロックでオブジェクトを破棄したいようです。つまり、スマートまたは一意のポインターが必要なことを行います。

std::unique_ptr<Object> obj(new Object());

または最新の C++

auto obj = std::make_unique<Object>();

どの例外がスローされても、オブジェクトは破棄されます。RAII に戻ると、この場合、リソース割り当てはオブジェクトにメモリを割り当てて構築することであり、初期化は unique_ptr の初期化です。

于 2008-12-24T02:07:37.377 に答える
12

いいえ。最終的に似た方法を構築する標準的な方法は、懸念を分離し ( http://en.wikipedia.org/wiki/Separation_of_concerns )、try ブロック内で使用されるオブジェクトが自動的にデストラクタでリソースを解放するようにすることです ("スコープバウンドリソース管理」)。Java とは異なり、デストラクタは決定論的に実行されるため、デストラクタに依存して安全にクリーンアップできます。このようにして、リソースを取得したオブジェクトもリソースをクリーンアップします。

特別な方法の 1 つは、動的メモリ割り当てです。リソースを取得するのはあなたなので、もう一度クリーンアップする必要があります。ここでは、スマート ポインターを使用できます。

try {
    // auto_ptr will release the memory safely upon an exception or normal 
    // flow out of the block. Notice we use the "const auto_ptr idiom".
    // http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
    std::auto_ptr<A> const aptr(new A);
} 
// catch...
于 2008-12-24T02:08:52.353 に答える
5

奇妙な理由で標準ライブラリにアクセスできない場合、リソースを処理するためにスマート ポインター型を必要なだけ実装するのは非常に簡単です。少し冗長に見えるかもしれませんが、ネストされた try/catch ブロックよりもコードが少なく、このテンプレートを定義する必要があるのは、管理が必要なリソースごとに 1 回ではなく、一度だけです。

template<typename T>
struct MyDeletable {
    explicit MyDeletable(T *ptr) : ptr_(ptr) { }
    ~MyDeleteable() { delete ptr_; }
private:
    T *ptr_;
    MyDeletable(const MyDeletable &);
    MyDeletable &operator=(const MyDeletable &);
};

void myfunction() {
    // it's generally recommended that these two be done on one line.
    // But it's possible to overdo that, and accidentally write
    // exception-unsafe code if there are multiple parameters involved.
    // So by all means make it a one-liner, but never forget that there are
    // two distinct steps, and the second one must be nothrow.
    Object *myObject = new Object();
    MyDeletable<Object> deleter(myObject);

    // do something with my object

    return;
}

もちろん、これを行ってからコードの残りの部分で RAII を使用すると、最終的には標準およびブースト スマート ポインター型のすべての機能が必要になります。しかし、これは始まりであり、あなたが望むと私が思うことをします。

try ... catch アプローチは、メンテナンス プログラミングに直面するとうまくいかないでしょう。CLEAN UP ブロックが実行されることは保証されていません。たとえば、「何かを行う」コードが早期に返された場合、または例外ではない何かがスローされた場合などです。一方、私のコードの「deleter」のデストラクタは、両方のケースで実行されることが保証されています (ただし、プログラムが終了した場合はそうではありません)。

于 2008-12-24T02:44:47.710 に答える
5

私のアドバイスは、C++ の try-finally 句の動作をエミュレートしようとしないことです。代わりに RAII を使用してください。あなたはもっと幸せに暮らせるでしょう。

于 2008-12-24T14:14:09.127 に答える
3

ポインター myObject を削除してメモリリークを回避しようとしていると仮定すると、コードに「return」ステートメントがある場合、コードはこれを実行できない可能性があります// Do something with myObject.(実際のコードがここにあると想定しています)。

RAII 手法には、特定のオブジェクトのデストラクタで、「最終的に」ブロックに相当する関連アクションがあります。

class ResourceNeedingCleanup
{
  private:
    void cleanup(); // action to run at end
  public:
    ResourceNeedingCleanup( /*args here*/) {}
    ~ResourceNeedingCleanup() { cleanup(); }  

    void MethodThatMightThrowException();
};

typedef boost::shared_ptr<ResourceNeedingCleanup> ResourceNeedingCleanupPtr;
// ref-counted smart pointer


class SomeObjectThatMightKeepReferencesToResources
{
   ResourceNeedingCleanupPtr pR;

   void maybeSaveACopy(ResourceNeedingCleanupPtr& p)
   {
      if ( /* some condition is met */ )
         pR = p;
   }
};

// somewhere else in the code:
void MyFunction(SomeObjectThatMightKeepReferencesToResources& O)
{
   ResourceNeedingCleanup R1( /*parameters*/) ;
   shared_ptr<ResourceNeedingCleanup> pR2 = 
        new ResourceNeedingCleanup( /*parameters*/ );
   try
   {
      R1.MethodThatMightThrowException();
      pR2->MethodThatMightThrowException();
      O->maybeSaveACopy(pR2);
   }
   catch ( /* something */ )
   {
      /* something */
   }

   // when we exit this block, R1 goes out of scope and executes its destructor
   // which calls cleanup() whether or not an exception is thrown.
   // pR2 goes out of scope. This is a shared reference-counted pointer. 
   // If O does not save a copy of pR2, then pR2 will be deleted automatically
   // at this point. Otherwise, pR2 will be deleted automatically whenever
   // O's destructor is called or O releases its ownership of pR2 and the
   // reference count goes to zero.
}

セマンティクスは正しいと思います。私自身は shared_ptr をあまり使用していませんが、auto_ptr<> よりも好きです。オブジェクトへのポインターは、1 つの auto_ptr<> だけが「所有」できます。私は COM のCComPtrと、shared_ptr<> に似ているが、1 つのスマートからのポインターの転送のために Attach() と Detach() を持つ「通常の」(非 COM) オブジェクト用に自分で作成したそのバリアントを使用しました。別へのポインタ。

于 2008-12-24T02:08:10.543 に答える
2

あなたの質問に直接答えるには、いいえ

その機能を実装するのは賢明な方法ですが、信頼性は高くありません。あなたが失敗する1つの方法は、あなたの「何かをする」コードがから派生していない例外をスローする場合ですException. その場合、決してdelete myObject.

ここにはもっと重要な問題があり、それは特定の言語のプログラマーが採用する方法論です。あなたがRAIIについて耳にする理由は、あなたや私よりも経験豊富なプログラマーが、C++ プログラミングの分野でその方法論が信頼できることに気付いたからです。あなたはそれを使用する他のプログラマーに頼ることができ、他のプログラマーはあなたがそれを使用することに頼りたいと思うでしょう.

于 2008-12-24T02:48:22.990 に答える