我为 C++11 编写了一个小型实用程序类,我将其用作范围保护,以便更轻松地处理异常安全和类似的事情。
看起来有点像黑客。但我很惊讶我没有在其他地方看到它使用 C++11 功能。我认为 boost 对于 C++98 有类似的东西。
但这是个好主意吗?还是有我忽略的潜在问题? boost 或类似中是否已经有类似的解决方案(具有 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;
}
但这是个好主意吗?
当然。一个相关主题是RAII 范式。
或者有吗 我错过了潜在的问题吗?
你不处理异常。
是 已经有一个类似的解决方案(与 C++0x 功能)在 boost 或类似的?
Alexandrescu 很久以前就想出了ScopeGuard。 Boost 和
std::tr1
都有一个名为 scoped_ptr
和 shared_ptr
(带有自定义删除器)的东西,可以让您完成此任务。
郑重声明,有 Boost ScopeExit。
瞄准镜守卫绝对是个好主意。我认为范围保护概念是异常安全的有力工具。如果您可以使用 C++0x 语法制作一个比 Boost 的 ScopeExit 更安全、更简洁的版本,我认为这将非常值得您花时间。
与 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
。哎呀,甚至 pthreads 也提供了一个基本的范围保护,称为 pthread_cleanup_push。
当函数中有多个
scope(*)
语句时,作用域保护变得如此强大。它的扩展性非常好,而不是try/finally
,后者需要超人的力量来管理两个以上的任何东西。
如果用二元运算符替换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() == [&]
{
...
};
更新:
对应宏:
#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__]
目前使用此解决方案:
struct _tag_defer {
std::function<void()> fn;
_tag_defer() = default;
~_tag_defer() { fn(); }
void operator<<(std::function<void()> f) { fn = f; }
};
// clang-format off
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a ## b
#define defer_name CONCAT(__defer, __LINE__)
#define defer _tag_defer defer_name; defer_name << [&]
// clang-format on
像这样使用它:
{
defer { last_code_to_execute_on_scope_exit(); };
...
defer { first_code_to_execute_on_scope_exit(); };
}
使用
tr1::function
和 tr1::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.02 美元
struct at_scope_end
{
std::function < void () > Action;
at_scope_end (std::function < void () > Action) :
Action (Action)
{
}
~at_scope_end ()
{
Action ();
}
};
#define AT_SCOPE_END_CAT(x,y) x##y
#define AT_SCOPE_END_ID(index) AT_SCOPE_END_CAT(__sg, index)
#define AT_SCOPE_END(expr) at_scope_end AT_SCOPE_END_ID(__LINE__) ( [&] () { expr; } );
我们可以通过将其放在定义中来省略丑陋的 [&] 内容:
#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) 进行测试。问候。
这是一个好主意,但是你的课程存在一些问题。
请注意,如果您实现第 2 点,您需要为实例化的每个作用域防护指定一个有意义的名称。一般来说,这不是问题,但它可能在您的应用程序中(或根据您的口味)。
最后,这个问题可能更适合CodeReview。
使用增强:
#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);
});
...
}
我有自己的瞄准镜。我不关心展开是由于异常还是非异常展开而发生。如果我做了一些更改,我最后会通过在范围保护上调用 ::disable() 来提交它们,从而在展开时禁用它。
使用我的作用域防护,可以链接多个作用域防护,并且如果链中的最后一个作用域防护被禁用,则在销毁时将其禁用状态传播到依赖的作用域防护。因此,如果您更改多个数据结构并希望以“事务”方式执行此操作,则您有多个范围保护,它们以相反的顺序逐步恢复这些更改,如果最后一切都很好,您可以禁用最后一个范围保护链和所有依赖的范围保护也被禁用。
这是我的代码:
#pragma once
#include <utility>
template<typename Fn>
struct invoke_on_destruct;
struct iod_base
{
private:
template<typename Fn>
friend struct invoke_on_destruct;
bool m_enabled;
iod_base *m_next;
iod_base( iod_base *next ) :
m_enabled( true ),
m_next( next )
{
}
void disable()
{
m_enabled = false;
}
};
template<typename Fn>
struct invoke_on_destruct final : public iod_base
{
private:
Fn m_fn;
public:
invoke_on_destruct( Fn &&fn, iod_base *next = nullptr )
requires requires( Fn fn ) { { fn() }; } :
iod_base( next ),
m_fn( std::forward<Fn>( fn ) )
{
}
~invoke_on_destruct()
{
bool enabled = m_enabled;
if( m_next )
m_next->m_enabled = enabled;
if( enabled )
m_fn();
}
using iod_base::disable;
};