7

リソースを初期化および解放するC関数ペアのRAIIラッパーを作成しましたが、ほとんどの場合に役立ちます。

#include <GL/glfw.h>
#include <string>
#include <functional>
#include <stdexcept>

template <typename UninitFuncType,
          typename SuccessValueType,
          SuccessValueType successValue>
class RAIIWrapper
{
public:
    template <typename InitFuncType, typename... Args>
    RAIIWrapper(InitFuncType initializer,
                UninitFuncType uninitializer,
                const std::string &errorString,
                const Args&... args) : 
        uninit_func(uninitializer)
    {
        if (successValue != initializer(args...))
            throw std::runtime_error(errorString);
        initialized = true;
    }

    bool isInitialized() const
    {
        return initalized;
    }

    ~RAIIWrapper()
    {
        if (initalized)
            uninit_func();
    }

    // non-copyable
    RAIIWrapper(const RAIIWrapper &) = delete;
    RAIIWrapper& operator=(const RAIIWrapper &) = delete;

private:
    bool initalized = false;
    std::function<UninitFuncType> uninit_func;
};

using GLFWSystem = RAIIWrapper<decltype(glfwTerminate), decltype(GL_TRUE), GL_TRUE>;
using GLFWWindow = RAIIWrapper<decltype(glfwCloseWindow), decltype(GL_TRUE), GL_TRUE>;

int main()
{
    GLFWSystem glfw(glfwInit,
                    glfwTerminate,
                    "Failed to initialize GLFW");
}

ただし、関数が戻ってきたときに、このクラスでどのvoidようEnter/LeaveCriticalSectionに実行すればよいかわからないとします。ケースに特化したクラスにする必要がありSuccessValueType = voidますか?または、デフォルトのテンプレートパラメータを使用して何かを行う必要がありますか?

4

2 に答える 2

5

私は注意したいのですが

  1. ラッパークラスの初期化関数に関する情報は必要ありません。初期化解除関数について知っておく必要があるだけです。

  2. ラッパーをインスタンス化する関数ヘルパーを作成できます。

私は次の解決策を思いつきました(@ipc例外処理のアイデアが好きでした)

template <typename UninitF>
struct RAII_wrapper_type
{
    RAII_wrapper_type(UninitF f)
    :_f(f), _empty(false)
    {}
    RAII_wrapper_type(RAII_wrapper_type&& r)
    :_f(r._f), _empty(false)
    {
      r._empty = true;
    }

    RAII_wrapper_type(const RAII_wrapper_type&) = delete;
    void operator=(const RAII_wrapper_type&) = delete;

    ~RAII_wrapper_type()
    {
      if (!_empty) {
        _f();
      }
    }

  private:
    UninitF _f;
    bool _empty; // _empty gets true when _f is `moved out` from the object.
};

template <typename InitF, typename UninitF, typename RType, typename... Args>
RAII_wrapper_type<UninitF> capture(InitF init_f, UninitF uninit_f, RType succ, 
                                   const char* error, Args... args)
{
  if(init_f(args...) != succ) {
    throw std::runtime_error(error);
  }
  return RAII_wrapper_type<UninitF>(uninit_f);
}

template<typename InitF, typename UninitF, typename... Args>
RAII_wrapper_type<UninitF> capture(InitF init_f, UninitF uninit_f, Args... args)
{
  init_f(args...);
  return RAII_wrapper_type<UninitF>(uninit_f);
}

例:

void t_void_init(int){}
int t_int_init(){ return 1; }
void t_uninit(){}

int main()
{
  auto t1 = capture(t_void_init, t_uninit, 7);
  auto t2 = capture(t_int_init, t_uninit, 0, "some error");
}

編集

RAII_wrapper_type移動セマンティクスが必要であり、その移動コンストラクターを慎重に実装して、uninit_f何度も呼び出されないようにする必要があります。

于 2013-01-19T22:04:23.567 に答える
3

リターンチェックとRAIIラッピングのロジックを分離します

template <typename UninitFuncType>
class RAIIWrapper
{
public:
  template <typename InitFuncType, typename... Args>
  RAIIWrapper(InitFuncType fpInitFunc,
              UninitFuncType fpUninitFunc,
              Args&&... args)
    : fpUninit(std::move(fpUninitFunc))
  {
    static_assert(std::is_void<decltype(fpInitFunc(args...))>::value, "ignored return value");
    fpInitFunc(std::forward<Args>(args)...);
  }

  bool isInitialized() const { return true; } // false is impossible in your implementation

  ~RAIIWrapper() { fpUninit(); } // won't be called if constructor throws

private:
  UninitFuncType fpUninit; // avoid overhead of std::function not needed
};

template <typename InitFuncType, typename UninitFuncType, typename... Args>
RAIIWrapper<UninitFuncType>
raiiWrapper(InitFuncType fpInitFunc,
            UninitFuncType fpUninitFunc,
            Args&&... args)
{
  return RAIIWrapper<typename std::decay<UninitFuncType>::type>
    (std::move(fpInitFunc), std::move(fpUninitFunc), std::forward<Args>(args)...);
}

template <typename InitFuncType, typename SuccessValueType>
struct ReturnChecker {
  InitFuncType func;
  SuccessValueType success;
  const char *errorString;
  ReturnChecker(InitFuncType func,
                SuccessValueType success,
                const char *errorString)
    : func(std::move(func)), success(std::move(success)), errorString(errorString) {}

  template <typename... Args>
  void operator()(Args&&... args)
  {
    if (func(std::forward<Args>(args)...) != success)
      throw std::runtime_error(errorString);
  }
};
template <typename InitFuncType, typename SuccessValueType,
          typename Ret = ReturnChecker<InitFuncType, SuccessValueType> >
Ret checkReturn(InitFuncType func, SuccessValueType success, const char *errorString)
{
  return Ret{func, success, errorString};
}

また、型の推定を可能にする関数を追加しました。使用方法は次のとおりです。

auto _ = raiiWrapper(checkReturn(glfwInit, GL_TRUE, "Failed to initialize GLFW"),
                     glfwTerminate);

void以外の戻り値を持つ関数オブジェクトがあると、static_assertが失敗するため、次のことは不可能です。

raiiWrapper(glfwInit, glfwTerminate); // fails compiling

本当に無視したい場合は、ignoreReturn関数オブジェクトを追加できます。また、独自のリターンコードチェッカーを作成できるため、リターンコードチェックは必要に応じて高度化できることに注意してください(成功は偶数である必要があります)。

于 2013-01-19T21:39:18.307 に答える