webentwicklung-frage-antwort-db.com.de

Ist es möglich, das Verhalten der Funktion basierend auf dem Umfang zu ändern?

Ich möchte etwas Ähnliches wie Rust unsafe scope in C++ erstellen .. Die Idee ist, dass ich einige Funktionen habe, die eine Anzahl von Prüfungen durchführen. Zum Beispiel:

void check() {
     if (...)
        throw exception(...);

}

void foo() {
     check();

     // do some work
}

Nun möchte ich die Funktion foo () mit oder (in einem anderen Kontext) aufrufen können, ohne diese Prüfungen durchzuführen. Im Idealfall würde es so aussehen:

foo(); // call foo and perform checks
unsafe {
    foo(); // call foo without checks
}

Meine Frage ist: Kann man so etwas in der Kompilierzeit erreichen? Kann man die Funktion check irgendwie überprüfen (oder anders handeln), in welchem ​​Umfang sie aufgerufen wird?

Ich habe mir nur eine Runtime-Lösung ausgedacht: Um es in etwas Lambda einzuwickeln:

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

wo unsicher ist wie folgt implementiert:

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

die Funktion check () prüft lediglich das Flag thread_local und führt nur dann Prüfungen durch, wenn sie auf safe gesetzt ist.

6
Igor

????

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

Live auf Coliru sehen . Definieren Sie unsafe auch nicht als Makro ...

6
Quentin

kann man so etwas in der Kompilierzeit erreichen?

Nicht so, wie du dich vorgestellt hast. Wenn Sie eine Vorlagenfunktion für foo erstellen, erhalten Sie möglicherweise gleichwertige Ergebnisse:

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
}

Man könnte es so nennen:

foo();
foo<CallType::UNSAFE>();

Ablehnung von Vorlagen?

Einfacher Ansatz (Danke, @ VTT ):

void check(); // no template any more

void foo_unsafe()
{
    // do some work
}
inline void foo()
{
    check();
    foo_unsafe();
}

Oder über Parameter auswählen (dieses Muster existiert auch in der Standardbibliothek):

struct Unsafe
{
};
inline Unsafe unsafe;

void check();

void foo(Unsafe)
{
    // do some work
}
inline void foo()
{
    check();
    foo(unsafe);
}

Bearbeiten:

In dem von mir vorgestellten Beispiel könnte ich das tun, aber im Allgemeinen kann ich eine andere Funktionsleiste innerhalb von unsicher aufrufen, die wiederum foo nennt. Und ich möchte mich nicht auf Riegel und andere mögliche Methoden spezialisieren.

Unter dieser Einschränkung ist die Vorlagenvariante möglicherweise die nächste, die Sie zur Zeit compile erreichen können. Sie müssen nicht alle Funktionen spezialisieren, aber Sie müssen Vorlagen erstellen aus:

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

Ich würde einfach einen RAII-Typ verwenden, um das unsichere Flag innerhalb eines Bereichs als solchen umzuschalten:

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

Um es zu verschachteln, füge ich einen Referenzzähler hinzu:

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

Der ref_count muss nicht atomar sein, da er thread_local ist

Ich glaube nicht, dass es eine Möglichkeit gibt, die gewünschte Syntax mit der unsafe vor dem Gültigkeitsbereich zu erreichen, aber wenn Sie es nach dem Gültigkeitsbereich als solchen setzen, sollte es ungefähr gleich sein:

{ unsafe_scope cur_scope;
    return get_value(x); // Performs the check
}

Bearbeiten:

Mir ist jetzt aufgefallen, dass Quentins Antwort auch ein RAII-Typ ist, nur mit etwas anderer Semantik. Anstelle eines globalen thread_local-Flags gibt eine Funktion nur dann zurück, wenn der Referenzzähler größer als 0 ist Es ist auch mit diesem unsafe_scope möglich, indem sein Makro folgendermaßen geändert wird:

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

Seine Methode verwendet den if-Initialisierer von C++ 17, mit dem Sie eine Variable in der if-Anweisung initiieren können. Die Variable wird jedoch weiterhin im else-Block initialisiert, sodass sie nur nach dem else-Gültigkeitsbereich zerstört wird, wenn sie beendet ist.

1

Es ist auch möglich, template Teilspezialisierung statt Präprozessor zu verwenden.

Zum Beispiel:

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

Ausgänge:

Invalid argument
{1,0,1}
{1,-1,1}

Process returned 0 (0x0)   execution time : 0.053 s
Press any key to continue.
0
Victor Gubin