可以根据范围更改功能的行为吗?

时间:2018-11-30 08:15:02

标签: c++ c++11 template-meta-programming

我想在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时执行检查。

4 个答案:

答案 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.