0

オブジェクトの状態を進める場合、std::swap単純なオブジェクトとポインタのスワップには を使用するとうまく機能します。他のインプレース アクションの場合Boost.ScopeExitはかなりうまく機能しますが、関数間で終了ハンドラーを共有したい場合はそれほどエレガントではありません。似たようなことを達成するためのC++ 11ネイティブの方法はありますBoost.ScopeExitが、より良いコードの再利用を可能にしますか?

4

1 に答える 1

1

(Ab)std::unique_ptrのカスタム Deleter を aScopeExitVisitorまたは Post Condition として使用します。~7 行目までスクロールして、main()これが呼び出しサイトで実際にどのように使用されているかを確認します。次の例では、パラメーターを必要としない/のstd::functionまたは ラムダを許可し、 /にパラメーターを渡す必要がある場合はネストされたクラスを許可します。DeleterScopeExitVisitorDeleterScopeExitVisitor

#include <iostream>
#include <memory>

class A {
 public:
  using Type = A;
  using Ptr = Type*;
  using ScopeExitVisitorFunc = std::function<void(Ptr)>;
  using ScopeExitVisitor = std::unique_ptr<Type, ScopeExitVisitorFunc>;

  // Deleters that can change A's private members. Note: Even though these
  // are used as std::unique_ptr<> Deleters, these Deleters don't delete
  // since they are merely visitors and the unique_ptr calling this Deleter
  // doesn't actually own the object (hence the label ScopeExitVisitor).
  static void ScopeExitVisitorVar1(Ptr aPtr) {
    std::cout << "Mutating " << aPtr << ".var1. Before: " << aPtr->var1;
    ++aPtr->var1;
    std::cout << ", after: " << aPtr->var1 << "\n";
  }

  // ScopeExitVisitor accessing var2_, a private member.
  static void ScopeExitVisitorVar2(Ptr aPtr) {
    std::cout << "Mutating " << aPtr << ".var2. Before: " << aPtr->var2_;
    ++aPtr->var2_;
    std::cout << ", after: " << aPtr->var2_ << "\n";
  }

  int var1 = 10;
  int var2() const { return var2_; }

  // Forward declare a class used as a closure to forward Deleter parameters
  class ScopeExitVisitorParamVar2;

 private:
  int var2_ = 20;
};

// Define ScopeExitVisitor closure. Note: closures nested inside of class A
// still have access to private variables contained inside of A.
class A::ScopeExitVisitorParamVar2 {
 public:
  ScopeExitVisitorParamVar2(int incr) : incr_{incr} {}
  void operator()(Ptr aPtr) {
    std::cout << "Mutating " << aPtr << ".var2 by " << incr_ << ". Before: " << aPtr->var2_;
    aPtr->var2_ += incr_;
    std::cout << ", after: " << aPtr->var2_ << "\n";
  }

 private:
  int incr_ = 0;
};

// Can also use lambdas, but in this case, you can't access private
// variables.
//
static auto changeStateVar1Handler = [](A::Ptr aPtr) {
  std::cout << "Mutating " << aPtr << ".var1 " << aPtr->var1 << " before\n";
  aPtr->var1 += 2;
};

int main() {
  A a;

  std::cout << "a: " << &a << "\n";

  std::cout << "a.var1: " << a.var1 << "\n";
  std::cout << "a.var2: " << a.var2() << "\n";

  { // Limit scope of the unique_ptr handlers. The stack is unwound in
    // reverse order (i.e. Deleter var2 is executed before var1's Deleter).
    A::ScopeExitVisitor scopeExitVisitorVar1(nullptr, A::ScopeExitVisitorVar1);
    A::ScopeExitVisitor scopeExitVisitorVar1Lambda(&a, changeStateVar1Handler);
    A::ScopeExitVisitor scopeExitVisitorVar2(&a, A::ScopeExitVisitorVar2);
    A::ScopeExitVisitor scopeExitVisitorVar2Param(nullptr, A::ScopeExitVisitorParamVar2(5));

    // Based on the control of a function and required set of ScopeExitVisitors that
    // need to fire use release() or reset() to control which visitors are used.
    // Imagine unwinding a failed but complex API call.
    scopeExitVisitorVar1.reset(&a);
    scopeExitVisitorVar2.release(); // Initialized in ctor. Use release() before reset().
    scopeExitVisitorVar2.reset(&a);
    scopeExitVisitorVar2Param.reset(&a);

    std::cout << "a.var1: " << a.var1 << "\n";
    std::cout << "a.var2: " << a.var2() << "\n";
    std::cout << "a.var2: " << a.var2() << "\n";
  }

  std::cout << "a.var1: " << a.var1 << "\n";
  std::cout << "a.var2: " << a.var2() << "\n";
}

生成するもの:

a: 0x7fff5ebfc280
a.var1: 10
a.var2: 20
a.var1: 10
a.var2: 20
a.var2: 20
Mutating 0x7fff5ebfc280.var2 by 5. Before: 20, after: 25
Mutating 0x7fff5ebfc280.var2. Before: 25, after: 26
Mutating 0x7fff5ebfc280.var1 10 before
Mutating 0x7fff5ebfc280.var1. Before: 12, after: 13
a.var1: 13
a.var2: 26

プラス面として、このトリックは次の理由で優れています。

  • Deleter で使用されるコードは、プライベート変数にアクセスできます
  • デリーターコードは一元化可能
  • public メンバーにしかアクセスできませんが、ラムダを使用することは引き続き可能です。
  • パラメーターは、クロージャーとして機能するネストされたクラスを介して Deleter に渡すことができます
  • すべてのstd::unique_ptrインスタンスにオブジェクトを割り当てる必要があるわけではありません (たとえば、不要な Deleters を に設定したままにしても問題ありませんnullptr) 。
  • 実行時に動作を変更するには、単に呼び出しreset()またはrelease()
  • スタックを構築する方法に基づいて、コンパイル時に、std::unique_ptr(s) のスコープがスコープ外になったときにオブジェクトの安全保証を変更することができます。

最後に、使用Boost.ScopeExitすると、呼び出しをヘルパー関数に転送したり、Boost.ScopeExitドキュメントで提案されているものと同様の条件を使用したりできますbool commit = ...;。次のようなもの:

#include <iostream>
#include <boost/scope_exit.hpp>

int main() {
  bool commitVar1 = false;
  bool commitVar2 = false;
  BOOST_SCOPE_EXIT_ALL(&) {
    if (commitVar1)
      std::cout << "Committing var1\n"
    if (commitVar2)
      std::cout << "Committing var2\n"
  };
  commitVar1 = true;
}

それには何の問題もありませんが、元の質問で尋ねられたように、呼び出しを別の場所にプロキシせずにコードを共有するにはどうすればよいですか? std::unique_ptrのデリータを として使用しScopeExitVisitorsます。

于 2013-11-03T06:55:34.840 に答える