web-dev-qa-db-ja.com

C ++ 11スコープ出口ガード、良い考えですか?

例外安全性などを簡単に処理するためのスコープガードとして使用するC++ 11用の小さなユーティリティクラスを作成しました。

ややハックのようです。しかし、C++ 11機能を使用している他の場所では見たことがないことに驚いています。ブーストはC++ 98でも似たようなものだと思います。

しかし、それは良い考えですか?それとも私が見逃した潜在的な問題がありますか?ブーストまたは同様のソリューション(C++ 11機能を備えた)はすでにありますか?

    namespace detail 
    {
        template<typename T>
        class scope_exit : boost::noncopyable
        {
        public:         
            explicit scope_exit(T&& exitScope) : exitScope_(std::forward<T>(exitScope)){}
            ~scope_exit(){try{exitScope_();}catch(...){}}
        private:
            T exitScope_;
        };          

        template <typename T>
        scope_exit<T> create_scope_exit(T&& exitScope)
        {
            return scope_exit<T>(std::forward<T>(exitScope));
        }
    }


#define _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line) name##line
#define _UTILITY_EXIT_SCOPE_LINENAME(name, line) _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line)
#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit(f)

そしてそれはのようなものを使用しています。

int main () 
{
  ofstream myfile;
  myfile.open ("example.txt");
  UTILITY_SCOPE_EXIT([&]{myfile.close();}); // Make sure to close file even in case of exception
  myfile << "Writing this to a file.\n"; // Imagine this could throw
  return 0;
}
24
ronag

しかし、それは良い考えですか?

承知しました。関連するトピックは RAIIパラダイム です。

それとも私が見逃した潜在的な問題がありますか?

例外は処理しません。

ブーストまたは同様のソリューション(C++ 0x機能を使用)はすでにありますか?

Alexandrescuは ScopeGuard ずっと前に思いついた。 Boostとstd::tr1の両方に、 scoped_ptr および shared_ptr (カスタム削除機能を使用)と呼ばれるものがあり、これを実行できます。 。

20
dirkgently

記録のために、 Boost ScopeExit があります。

18
Gregory Pakosz

スコープガードは間違いなく良い考えです。スコープガードの概念は、例外安全性のための強力なツールだと思います。 Boostの ScopeExit C++ 0x構文を使用して、より安全でクリーンなバージョンを作成できれば、時間の価値があると思います。

Alexandrescuの ScopeGuard およびBoostのScopeExitと同様に、 Dプログラミング言語 にはこの種の直接構文があります。 Dプログラミングチームは、スコープガードは十分に良いアイデアであると考え、スコープガードを追加しました 言語に直接 (つまり、ライブラリに実装されていません)。

例。

_void foo( bool fail )
{
   scope(exit)
   {
      writeln("I'm always printed");
   }

   scope(success) writeln("The function exited normally");

   scope(error)
      writeln("The function exited with an exception.");

   if( fail )
      throw new Exception("Die Die Die!");
}
_

スコープベースのガードは新しいものではありません。その機能は、クラスデストラクタ(RAIIなど)を使用して簡単に複製できます。 C#またはJavaでは_try/finally_に置き換えることもできます。ちなみに、pthreadでさえ、 pthread_cleanup_Push と呼ばれる基本的なスコープガードを提供します。

スコープガードを非常に強力にするのは、関数に複数のscope(*)ステートメントがある場合です。 2つ以上を管理するために超人的な力を必要とする_try/finally_とは対照的に、それは信じられないほどうまくスケーリングします。

9
deft_code

Create_scope_exitを二項演算子に置き換えると、括弧を削除できます。

class at_scope_exit
{
    template<typename F>
    struct scope_exit_fn_holder : boost::noncopyable
    {
        scope_exit_fn_holder(F&& f) : f(std::forward<F>(f)) {}

        F f;
        ~scope_exit_fn_holder() { f(); }
    };

    template<typename F>
    friend scope_exit_fn_holder<F> operator==(at_scope_exit, F&& f)
    {
        return scope_exit_fn_holder<F>(std::forward<F>(f));
    }
};

使用法:

auto atScopeExit = at_scope_exit() == [&]
{
    ...
};

pd:
対応するマクロ:

#include <boost/preprocessor/cat.hpp>

#define AT_SCOPE_EXIT auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [&]
#define AT_SCOPE_EXIT_EX(...) auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [__VA_ARGS__]
5
Abyx

Boostの使用:

#include <boost/preprocessor/cat.hpp>

template<class Fn>
class ScopeGuardDetails {
    const Fn m_fn;
public:
    constexpr ScopeGuardDetails(Fn &&fn) : m_fn(fn) {}
    ~ScopeGuardDetails() { m_fn(); }
};
#define ScopeGuardName BOOST_PP_CAT(BOOST_PP_CAT(__scope_guard, _), BOOST_PP_CAT(BOOST_PP_CAT(__LINE__, _), __COUNTER__))
#define defer(stmt) const auto ScopeGuardName = [](const auto _fn) { \
    return ScopeGuardDetails<decltype(_fn)> { std::move(_fn) }; \
}([&] { stmt });

使用法:

if (gdiplus::GdiplusStartup(&token, &startupInput, nullptr) == Gdiplus::Ok) {
    defer({
        gdiplus::GdiplusShutdown(token);
    });
    ...
}
0
Steve Fan

これは良い考えですが、クラスにはいくつか問題があります。

  1. 新しい演算子を無効にする必要があります(これを強制的に削除するような方法でユーザーが使用する必要はありませんよね?)
  2. これを単純なRAIIではなく スコープガード にするためには、「コミット」関数が必要です。

ポイント2を実装する場合は、インスタンス化するスコープガードごとに意味のある名前が必要であることに注意してください。これは一般に問題ではありませんが、アプリケーション(または好み)にある可能性があります。

最後に、この質問はおそらく CodeReview に適していたでしょう。

0
Stefano Falasca

以下のように、tr1::functiontr1::unique_ptrを使用して実装を非常に簡略化できます。

namespace detail
{
    class ScopeGuard
    {
    public:
        explicit ScopeGuard(std::function<void()> onExitScope) 
            : onExitScope_(onExitScope), dismissed_(false)
        { }

        ~ScopeGuard()
        {
            try
            {
                if(!dismissed_)
                {
                    onExitScope_();
                }
            }
            catch(...){}
        }

        void Dismiss()
        {
            dismissed_ = true;
        }
    private:
        std::function<void()> onExitScope_;
        bool dismissed_;

        // noncopyable
    private:
        ScopeGuard(ScopeGuard const&);
        ScopeGuard& operator=(ScopeGuard const&);
    };
}

inline std::unique_ptr<detail::ScopeGuard> CreateScopeGuard(std::function<void()> onExitScope)
{
    return std::unique_ptr<detail::ScopeGuard>(new detail::ScopeGuard(onExitScope));
}
0
Weipeng L

醜い[&]のものは、定義に入れることで省略できます。

#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit([&]f)

次に:

UTILITY_SCOPE_EXIT({myfile.close();});

MSVC++ 11.0(VS2012)でテスト済み。よろしく。

0
bobef