最简单和最整洁的C ++ 11 ScopeGuard [英] The simplest and neatest c++11 ScopeGuard

查看:237
本文介绍了最简单和最整洁的C ++ 11 ScopeGuard的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图写一个简单的 ScopeGuard基于Alexandrescu概念,但使用c ++ 11成语。

 命名空间RAII 
{
template< typename Lambda>
class ScopeGuard
{
mutable bool committed;
Lambda rollbackLambda;
public:

ScopeGuard(const Lambda& _l):committed(false),rollbackLambda(_l){}

template< typename AdquireLambda>
ScopeGuard(const AdquireLambda& _al,const Lambda& _l):committed(false),rollbackLambda(_l)
{
_al
}

〜ScopeGuard()
{
if(!committed)
rollbackLambda
}
inline void commit()const {committed = true; }
};

template< typename aLambda,typename rLambda>
const ScopeGuard< rambda& makeScopeGuard(const aLambda& _a,const rLambda& _r)
{
return ScopeGuard< (_a,_r);
}

template< typename rLambda>
const ScopeGuard< rambda& makeScopeGuard(const rLambda& _r)
{
return ScopeGuard< (r);
}
}

这里是用法:

  void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptions()
{
std :: vector< int> myVec;
std :: vector< int> someOtherVec;

myVec.push_back(5);
//第一个构造函数,adquire发生在其他地方
const auto& a = RAII :: makeScopeGuard([&](){myVe​​c.pop_back();});

// sintactically neater,因为一切都发生在一行
const auto& b = RAII :: makeScopeGuard([&](){someOtherVec.push_back(42);}
,[&](){someOtherVec.pop_back

b.commit();
a.commit();因为我的版本比大多数例子(如Boost ScopeExit)要短得多,因为我的版本比大多数例子要短(例如Boost ScopeExit)我想知道什么专业我被抛弃。希望我在一个80/20的情况下(我得到80%的整洁与20%的代码行),但我不禁想知道,如果我错过了一些重要的东西,或有一些缺点值提示此版本的ScopeGuard成语



谢谢!



编辑与makeScopeGuard的一个非常重要的问题,它在构造函数中接受adquire lambda。如果adquire lambda throws,则释放lambda从不被调用,因为范围保护从未完全构建。在许多情况下,这是所需的行为,但我觉得有时一个版本,如果发生投掷也会调用回滚。

  //警告:如果adquire lambda不抛出,只有安全,否则释放lambda从未被调用,因为范围保护从未完成initialistion .. 
template< typename aLambda,typename rLambda>
ScopeGuard< r。 //返回值是首选的C ++ 11方式。
makeScopeGuardThatDoesNOTRollbackIfAdquireThrows(aLambda&& _a,rLambda&& _r)//再次完美转发
{
return ScopeGuard< (std :: forward a(_ a),std :: forward r(r)); // ***不再是UB,因为我们返回值
}

template< typename aLambda,typename rLambda>
ScopeGuard< r。 //返回值是首选的C ++ 11方式。
makeScopeGuardThatDoesRollbackIfAdquireThrows(aLambda&& _a,rLambda&& _r)//再次完美转发
{
auto scope = ScopeGuard< (std :: forward r(r)); // ***不再是UB,因为我们返回值
_a();
return scope;
}






想要在这里放入完整的代码,包括测试:






  #include< ; vector> 

命名空间RAII
{

template< typename Lambda>
class ScopeGuard
{
bool committed;
Lambda rollbackLambda;
public:

ScopeGuard(const ScopeGuard& _sc):committed(false),rollbackLambda(_l){}

(false),rollbackLambda(_sc.rollbackLambda)
{
if(_sc.committed)
committed = true;
else
_sc.commit();
}

ScopeGuard(ScopeGuard& _sc):committed(false),rollbackLambda(_sc.rollbackLambda)
{
if(_sc.committed)
committed = true;
else
_sc.commit();
}

//警告:如果adquire lambda不抛出,只有安全,否则释放lambda从未被调用,因为范围保护从未完成initialistion ..
template< typename AdquireLambda>
ScopeGuard(const AdquireLambda& _al,const Lambda& _l):committed(false),rollbackLambda(_l)
{
std :: forward< AdquireLambda&
}

//警告:如果adquire lambda不抛出,只有安全,否则释放lambda从未被调用,因为范围保护从未完成initialistion ..
template< typename AdquireLambda,typename L>
ScopeGuard(AdquireLambda&& _al,L&& _l):committed(false),rollbackLambda(std :: forward L(_ l))
{
std :: forward ; AdquireLambda(_al)(); //如果函子有&& _qualified operator()
}


〜ScopeGuard()
{
if commit)
rollbackLambda();
}
inline void commit(){committed = true; }
};


//警告:如果adquire lambda不抛出,只有安全,否则释放lambda从未被调用,因为范围保护从未完成初始化。
template< typename aLambda,typename rLambda>
ScopeGuard< r。 //返回值是首选的C ++ 11方式。
makeScopeGuardThatDoesNOTRollbackIfAdquireThrows(aLambda&& _a,rLambda&& _r)//再次完美转发
{
return ScopeGuard< (std :: forward a(_ a),std :: forward r(r)); // ***不再是UB,因为我们返回值
}

template< typename aLambda,typename rLambda>
ScopeGuard< r。 //返回值是首选的C ++ 11方式。
makeScopeGuardThatDoesRollbackIfAdquireThrows(aLambda&& _a,rLambda&& _r)//再次完美转发
{
auto scope = ScopeGuard< (std :: forward r(r)); // ***不再是UB,因为我们返回值
_a();
return scope;
}

template< typename rLambda>
ScopeGuard< r。 makeScopeGuard(rLambda&& _r)
{
return ScopeGuard< (std :: forward r(r));
}

命名空间basic_usage
{
struct Test
{

std :: vector< int> myVec;
std :: vector< int> someOtherVec;
bool shouldThrow;
void run()
{
shouldThrow = true;
try
{
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
} catch(...)
{
AssertMsg(myVec.size()== 0&& someOtherVec.size()== 0,rollback does not work ;
}
shouldThrow = false;
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
AssertMsg(myVec.size()== 1&&& someOtherVec.size()== 1,意外结束状态);
shouldThrow = true;
myVec.clear(); someOtherVec.clear();
try
{
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows();
} catch(...)
{
AssertMsg(myVec.size()== 0&& someOtherVec.size()== 0,rollback does not work) ;
}
}

void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows()// throw()
{

myVec.push_back(42);
auto a = RAII :: makeScopeGuard([&](){HAssertMsg(myVec.size()> 0,试图在空myVec中调用pop_back()); myVec.pop_back );

auto b = RAII :: makeScopeGuardThatDoesNOTRollbackIfAdquireThrows([&](){someOtherVec.push_back(42);}
,[&](){HAssertMsg(myVec.size > 0,尝试调用pop_back()在空someOtherVec); someOtherVec.pop_back();});

if(shouldThrow)throw 1;

b.commit();
a.commit();
}

void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows()// throw()
{
myVec.push_back(42);
auto a = RAII :: makeScopeGuard([&](){HAssertMsg(myVec.size()> 0,试图在空myVec中调用pop_back()); myVec.pop_back );

auto b = RAII :: makeScopeGuardThatDoesRollbackIfAdquireThrows([&](){someOtherVec.push_back(42); if(shouldThrow)throw 1;}
,[& HAssertMsg(myVec.size()> 0,试图调用pop_back()在空的someOtherVec); someOtherVec.pop_back();});

b.commit();
a.commit();
}
};
}
}


解决方案

.ScopeExit是一个需要使用非C ++ 11代码的宏,即无法访问语言中lambdas的代码。它使用一些聪明的模板黑客(比如滥用模板和比较运算符使用< 引起的歧义)和预处理器来模拟lambda特性。



显示的代码也是错误的(这可能是使用现有解决方案的最强的原因):它调用未定义的行为,由于返回引用



由于您尝试使用C ++ 11功能,因此可以通过使用移动语义,右值引用和完美转发来改进代码:

 模板< typename Lambda> 
class ScopeGuard
{
bool committed; // not mutable
Lambda rollbackLambda;
public:


//确保这不是复制ctor
模板< typename L,
DisableIf< std :: is_same< RemoveReference< ; RemoveCv L>,ScopeGuard L>> = _
>
/ *有关DisableIf
*的信息,请参见http://loungecpp.net/w/EnableIf_in_C%2B%2B11
*和http://stackoverflow.com/q/10180552/46642。 /
显式ScopeGuard(L&& _l)
//显式,除非你想从*一切*
:committed(false)
,rollbackLambda forward(L)(_l))//避免复制,除非有必要
{}

template< typename AdquireLambda,typename L>
ScopeGuard(AdquireLambda&& _al,L&& _l):committed(false),rollbackLambda(std :: forward L(_ l))
{
std :: forward ; AdquireLambda(_al)(); //只是为了防止函子有&& _qualified operator()
}

//移动构造函数
ScopeGuard(ScopeGuard&&&)
:commit(that.committed)
,rollbackLambda(std :: move(that.rollbackLambda)){
that.committed = true;
}

〜ScopeGuard()
{
if(!committed)
rollbackLambda //如果这抛出了怎么办?
}
void commit(){committed = true; } //不需要const
};

template< typename aLambda,typename rLambda>
ScopeGuard< r。 //返回值是首选的C ++ 11方式。
makeScopeGuard(aLambda&& _a,rLambda&& _r)//再次完美转发
{
return ScopeGuard< (std :: forward a(_ a),std :: forward r(r)); // ***不再是UB,因为我们返回值
}

template< typename rLambda>
ScopeGuard< r。 makeScopeGuard(rLambda&& _r)
{
return ScopeGuard< (std :: forward r(r));
}


I'm attempting to write a simple ScopeGuard based on Alexandrescu concepts but with c++11 idioms.

namespace RAII
{
    template< typename Lambda >
    class ScopeGuard
    {
        mutable bool committed;
        Lambda rollbackLambda; 
        public:

            ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}

            template< typename AdquireLambda >
            ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)
            {
                _al();
            }

            ~ScopeGuard()
            {
                if (!committed)
                    rollbackLambda();
            }
            inline void commit() const { committed = true; }
    };

    template< typename aLambda , typename rLambda>
    const ScopeGuard< rLambda >& makeScopeGuard( const aLambda& _a , const rLambda& _r)
    {
        return ScopeGuard< rLambda >( _a , _r );
    }

    template<typename rLambda>
    const ScopeGuard< rLambda >& makeScopeGuard(const rLambda& _r)
    {
        return ScopeGuard< rLambda >(_r );
    }
}

Here is the usage:

void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptions() 
{
   std::vector<int> myVec;
   std::vector<int> someOtherVec;

   myVec.push_back(5);
   //first constructor, adquire happens elsewhere
   const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );  

   //sintactically neater, since everything happens in a single line
   const auto& b = RAII::makeScopeGuard( [&]() { someOtherVec.push_back(42); }
                     , [&]() { someOtherVec.pop_back(); } ); 

   b.commit();
   a.commit();
}

Since my version is way shorter than most examples out there (like Boost ScopeExit) i'm wondering what specialties i'm leaving out. Hopefully i'm in a 80/20 scenario here (where i got 80 percent of neatness with 20 percent of lines of code), but i couldn't help but wonder if i'm missing something important, or is there some shortcoming worth mentioning of this version of the ScopeGuard idiom

thanks!

Edit I noticed a very important issue with the makeScopeGuard that takes the adquire lambda in the constructor. If the adquire lambda throws, then the release lambda is never called, because the scope guard was never fully constructed. In many cases, this is the desired behavior, but i feel that sometimes a version that will invoke rollback if a throw happens is desired as well:

//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
    return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}

template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
    auto scope = ScopeGuard< rLambda >(std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
    _a();
    return scope;
}


so for completeness, i want to put in here the complete code, including tests:


#include <vector>

namespace RAII
{

    template< typename Lambda >
    class ScopeGuard
    {
        bool committed;
        Lambda rollbackLambda; 
        public:

            ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}

            ScopeGuard( const ScopeGuard& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda) 
            {
                if (_sc.committed)
                   committed = true;
                else
                   _sc.commit();
            }

            ScopeGuard( ScopeGuard&& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda)
            {
                if (_sc.committed)
                   committed = true;
                else
                   _sc.commit();
            }

            //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
            template< typename AdquireLambda >
            ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)
            {
               std::forward<AdquireLambda>(_al)();
            }

            //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
            template< typename AdquireLambda, typename L >
            ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l))
            {
                std::forward<AdquireLambda>(_al)(); // just in case the functor has &&-qualified operator()
            }


            ~ScopeGuard()
            {
                if (!committed)
                    rollbackLambda();
            }
            inline void commit() { committed = true; }
    };


    //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
    template< typename aLambda , typename rLambda>
    ScopeGuard< rLambda > // return by value is the preferred C++11 way.
    makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
    {
        return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
    }

    template< typename aLambda , typename rLambda>
    ScopeGuard< rLambda > // return by value is the preferred C++11 way.
    makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
    {
        auto scope = ScopeGuard< rLambda >(std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
        _a();
        return scope;
    }

    template<typename rLambda>
    ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
    {
        return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
    }

    namespace basic_usage
    {
        struct Test
        {

            std::vector<int> myVec;
            std::vector<int> someOtherVec;
            bool shouldThrow;
            void run()
            {
                shouldThrow = true;
                try
                {
                    SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
                } catch (...)
                {
                    AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");
                }
                shouldThrow = false;
                SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
                AssertMsg( myVec.size() == 1 && someOtherVec.size() == 1 , "unexpected end state");
                shouldThrow = true;
                myVec.clear(); someOtherVec.clear();  
                try
                {
                    SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows();
                } catch (...)
                {
                    AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");
                }
            }

            void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows() //throw()
            {

                myVec.push_back(42);
                auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );  

                auto b = RAII::makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); }
                                    , [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );

                if (shouldThrow) throw 1; 

                b.commit();
                a.commit();
            }

            void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows() //throw()
            {
                myVec.push_back(42);
                auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );  

                auto b = RAII::makeScopeGuardThatDoesRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); if (shouldThrow) throw 1; }
                                    , [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );

                b.commit();
                a.commit();
            }
        };
    }
}

解决方案

Boost.ScopeExit is a macro that needs to work with non-C++11 code, i.e. code that has no access to lambdas in the language. It uses some clever template hacks (like abusing the ambiguity that arises from using < for both templates and comparison operators!) and the preprocessor to emulate lambda features. That's why the code is longer.

The code shown is also buggy (which is probably the strongest reason to use an existing solution): it invokes undefined behaviour due to returning references to temporaries.

Since you're trying to use C++11 features, the code could be improved a lot by using move semantics, rvalue references and perfect-forwarding:

template< typename Lambda >
class ScopeGuard
{
    bool committed; // not mutable
    Lambda rollbackLambda; 
    public:


        // make sure this is not a copy ctor
        template <typename L,
                  DisableIf<std::is_same<RemoveReference<RemoveCv<L>>, ScopeGuard<Lambda>>> =_
        >
        /* see http://loungecpp.net/w/EnableIf_in_C%2B%2B11
         * and http://stackoverflow.com/q/10180552/46642 for info on DisableIf
         */
        explicit ScopeGuard(L&& _l)
        // explicit, unless you want implicit conversions from *everything*
        : committed(false)
        , rollbackLambda(std::forward<L>(_l)) // avoid copying unless necessary
        {}

        template< typename AdquireLambda, typename L >
        ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l))
        {
            std::forward<AdquireLambda>(_al)(); // just in case the functor has &&-qualified operator()
        }

        // move constructor
        ScopeGuard(ScopeGuard&& that)
        : committed(that.committed)
        , rollbackLambda(std::move(that.rollbackLambda)) {
            that.committed = true;
        }

        ~ScopeGuard()
        {
            if (!committed)
                rollbackLambda(); // what if this throws?
        }
        void commit() { committed = true; } // no need for const
};

template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuard( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
    return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}

template<typename rLambda>
ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
{
    return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
}

这篇关于最简单和最整洁的C ++ 11 ScopeGuard的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆