我想在C ++中创建类似于锈unsafe范围的东西。 我的想法是,我有一些执行检查数量的功能。例如:
void check() {
if (...)
throw exception(...);
}
void foo() {
check();
// do some work
}
现在,我希望能够使用或(在不同上下文中)调用函数foo()而不执行这些检查。理想情况下,它看起来应该像这样:
foo(); // call foo and perform checks
unsafe {
foo(); // call foo without checks
}
我的问题是,是否可以在编译时实现类似的功能?是否可以以某种方式从check
函数中检查(或采取不同的措施)函数在什么范围内被调用?
我只想出一个运行时解决方案:将其包装在lambda中:
unsafe([&] {
foo();
});
其中不安全的实现方式如下:
void unsafe(std::function<void()> f)
{
thread_local_flag = unsafe;
f();
thread_local_flag = safe;
}
check()函数将仅检查thread_local标志并仅在将其设置为safe
时执行检查。
答案 0 :(得分:6)
namespace detail_unsafe {
thread_local int current_depth;
struct unsafe_guard {
unsafe_guard() { ++current_depth; }
~unsafe_guard() { --current_depth; }
unsafe_guard(unsafe_guard const &) = delete;
unsafe_guard &operator = (unsafe_guard const &) = delete;
};
}
#define unsafe \
if(::detail_unsafe::unsafe_guard _ug; false) {} else
bool currently_unsafe() {
return detail_unsafe::current_depth > 0;
}
See it live on Coliru。另外,请不要实际将unsafe
定义为宏...
答案 1 :(得分:2)
有可能在编译时实现类似的东西吗?
不是您介绍的方式。不过,将foo用作模板函数可能会为您提供等效的结果,
enum class CallType // find a better name yourself...
{
SAFE,
UNSAFE,
};
template <CallType Type = CallType::SAFE>
void foo()
{
if constexpr(Type != CallType::UNSAFE)
{
if (...)
throw ...;
}
// do some work
}
您可以这样称呼它:
foo();
foo<CallType::UNSAFE>();
不喜欢模板吗?
简单方法(感谢@ VTT):
void check(); // no template any more
void foo_unsafe()
{
// do some work
}
inline void foo()
{
check();
foo_unsafe();
}
或通过参数选择(此模式也存在于标准库中):
struct Unsafe
{
};
inline Unsafe unsafe;
void check();
void foo(Unsafe)
{
// do some work
}
inline void foo()
{
check();
foo(unsafe);
}
编辑:
好吧,在我展示的示例中,我可以做到这一点,但是总的来说,我可以在不安全的内部调用其他功能栏,而该功能栏又会调用foo。而且我不想专门介绍bar和其他可能的方法。
在此约束下,模板变体可能是您在 compile 时可以获得的最接近的变体;您不必专门设计所有功能,但需要从以下模板制作模板:
template <CallType Type = CallType::SAFE>
void bar()
{
// do some other work
foo<Type>(); // just call with template parameter
// yet some further work
}
答案 2 :(得分:1)
我将仅使用RAII类型在范围内切换不安全标志,例如:
thread_local bool unsafe_flag = false;
/// RAII Type that toggles the flag on while it's alive
/// Possibly add a reference counter so it can be used nested
struct unsafe_scope
{
constexpr unsafe_scope() { unsafe_flag = true; }
~unsafe_scope() { unsafe_flag = false; }
};
/// Gets a value from a pointer
int get_value(int* ptr)
{
if ( unsafe_flag )
{
if ( ptr == nullptr ) { return 0; }
}
return *ptr;
}
int main()
{
int* x = nullptr;
//return get_value(x); // Doesn't perform the check
{
unsafe_scope cur_scope;
return get_value(x); // Performs the check
}
}
为了使其嵌套,我将添加一个参考计数器,如下所示:
/// RAII Type that toggles the flag on while it's alive
struct unsafe_scope
{
thread_local static size_t ref_count;
constexpr unsafe_scope()
{
unsafe_flag = true;
ref_count++;
}
~unsafe_scope()
{
ref_count--;
if ( ref_count == 0 ) { unsafe_flag = false; }
}
};
/// In source file
thread_local size_t unsafe_scope::ref_count = 0;
ref_count不需要是原子的,因为它是thread_local
现在,我不认为有一种方法可以在作用域之前使用unsafe
来实现所需的语法,但是如果将其放在作用域之后,则应该大致相同:
{ unsafe_scope cur_scope;
return get_value(x); // Performs the check
}
编辑:
我现在注意到Quentin的答案也是RAII类型,只是语义略有不同,而不是具有全局thread_local标志,而是在引用计数器大于0的情况下返回一个函数。此外,宏可以实现与您完全相同的语法想要的,尽管这个unsafe_scope
也可以这样修改他的宏:
#define unsafe\
if (unsafe_scope cur_scope; false) {} else
他的方法使用C ++ 17的if初始值设定项,它使您可以在if语句中初始化变量,但是该变量仍在else块中初始化,因此只有在else范围超过if范围后,它才会被销毁。
答案 3 :(得分:0)
还可以使用template partial specialization代替预处理器。
例如:
#include <iostream>
#include <type_traits>
#include <system_error>
template<class type>
struct unsafe
{};
template<>
struct unsafe<std::true_type>
{
private:
static void check_min() {}
template<typename T,typename ... Rest>
static void check_min(T first,Rest...rest) {
if(first < 0)
throw std::system_error( std::make_error_code(std::errc::invalid_argument) );
unsafe::check_min( rest... );
}
public:
template<class C,typename ... Args>
void operator()(C callback,Args... args) {
check_min( args... );
callback( args... );
}
};
template<>
struct unsafe<std::false_type>
{
template<class C,typename ... Args>
void operator()(C callback,Args...args) {
callback( args... );
}
};
class safe_context {
safe_context(const safe_context&) = delete;
safe_context& operator=(const safe_context&) = delete;
public:
static thread_local bool _context;
public:
constexpr safe_context() noexcept
{}
template<class C,typename ... Args>
void operator()(C callback,Args...args) {
if( _context )
unsafe< std::false_type >()(callback, args... );
else {
unsafe< std::true_type >()(callback, args... );
_context = true;
}
}
};
thread_local bool safe_context::_context = false;
int main ()
{
safe_context ctx;
// check with wrong args
try {
ctx( [](float x, float y, float z) {
std::cout << '{' << x << ',' << y << ',' << z << '}' << std::endl;
} , 1.0F, -1.0F, 1.0F);
} catch( std::exception& exc) {
std::clog << exc.what() << std::endl;
}
ctx( [](int x, int y, int z) {
std::cout << '{' << x << ',' << y << ',' << z << '}'<< std::endl;
},
1, 0, 1);
// will not trow, even when args are wrong
ctx( [](float x, float y, float z) {
std::cout << '{' << x << ',' << y << ',' << z << '}' << std::endl;
} , 1.0F, -1.0F, 1.0F);
return 0;
}
输出:
Invalid argument
{1,0,1}
{1,-1,1}
Process returned 0 (0x0) execution time : 0.053 s
Press any key to continue.