Можно ли изменить поведение функции в зависимости от области видимости? - PullRequest
0 голосов
/ 30 ноября 2018

Я хотел бы создать что-то похожее на область ржавчины небезопасный в C ++.Идея в том, что у меня есть несколько функций, выполняющих количество проверок.Например:

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, в какой области она называется?

Я придумал только решение времени выполнения: обернуть его в какую-нибудь лямбду:

unsafe([&] {
    foo();
});

, где небезопасный реализован следующим образом:

void unsafe(std::function<void()> f)
{
     thread_local_flag = unsafe;
     f();
     thread_local_flag = safe;
}

Функция check () будет просто проверять флаг thread_local и выполнять проверки только, когда он установлен на safe.

Ответы [ 4 ]

0 голосов
/ 30 ноября 2018

Я бы просто использовал тип 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
}

Редактировать:

Я заметил, что ответ Квентина также является типом RAII, только с немного другой семантикой,вместо того, чтобы иметь глобальный флаг thread_local, функция просто возвращает, если счетчик ссылок больше 0. Также макрос достигает точного синтаксиса, который вы хотели, хотя это также возможно с этим unsafe_scope, изменяя его макрос следующим образом:

#define unsafe\
    if (unsafe_scope cur_scope; false) {} else 

Его метод использует инициализатор if в C ++ 17, который позволяет вам инициировать переменную в операторе if, но переменная по-прежнему инициализируется в блоке else, поэтому яТ будет уничтожен только после остальной области видимости, если закончится.

0 голосов
/ 30 ноября 2018

?

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;
}

Смотрите его в прямом эфире на Coliru .Кроме того, пожалуйста, на самом деле не определяйте unsafe как макрос ...

0 голосов
/ 30 ноября 2018

Также возможно использовать шаблон частичной специализации вместо препроцессора.

Например:

#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.
0 голосов
/ 30 ноября 2018

возможно ли достичь чего-то подобного во время компиляции?

Не так, как вы представляли.Создание 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);
}

Редактировать:

Что ж, в представленном мною примере я мог бы это сделать, но в целом я могу вызвать некоторую другую функциональную панель внутри unsafe, которая, в свою очередь, вызывает foo.И я не хочу специализировать bar и другие возможные методы.

Без этого ограничения вариант шаблона может быть ближе всего к compile time;вам не нужно специализировать все функции, но вам нужно сделать шаблоны из:

template <CallType Type = CallType::SAFE>
void bar()
{
    // do some other work
    foo<Type>(); // just call with template parameter
    // yet some further work
}
...