11 Stimmen

Kann ich statisch verhindern, dass eine Funktion eine andere aufruft?

Ich habe die folgenden Schnittstellen:

class T {
public:
    // Called in parallel
    virtual unsigned validate () = 0;

    // Called with a lock taken out
    virtual unsigned update () = 0;
};

template 
class Cache {
public:
    // Ruft das angeforderte Objekt ab.
    // Wenn es nicht im Speicher existiert, gehe zu SQL.
    unsigned fetch (DataType &data);

    // Ruft das angeforderte Objekt ab.
    // Wenn es nicht im Speicher ist, gibt NOT_FOUND zurück.
    unsigned find (DataType &data);
};

Was ich erreichen möchte: Ich möchte, dass die Kompilierung fehlschlägt, wenn fetch während update aufgerufen wird. Effektiv möchte ich die Funktion statisch deaktivieren, basierend auf der Aufrufstelle. Etwas wie,

std::enable_if  
fetch (DataType &data);

Die Verwendung würde ungefähr so funktionieren:

class A_T : public T {
public:
    virtual unsigned validate () {
        global_cache.fetch (object); // OK
    }

    virtual unsigned update () {
        global_cache.find (object); // Auch OK

        global_cache.fetch (object); // FEHLER!
    }
};

Hintergrund

Es gibt ungefähr 500 Implementierungen von T in meinem Projekt.

Die Anwendung läuft in vielen Threads und ruft validate für viele Instanzen von T parallel auf. Dann wird ein globaler Lock genommen, und update wird aufgerufen. Daher ist die Geschwindigkeit von update entscheidend. Die allgemeine Einstellung ist, sich während validate so viel Zeit zu nehmen, wie benötigt wird, aber update sollte so schlank wie möglich sein.

Mein Problem liegt im Gebrauch von Cache. Ein Cache ist im Wesentlichen ein In-Memory-Cache von Datenobjekten aus SQL.

Die Richtlinie besagt, dass Cache::fetch niemals während update aufgerufen werden soll, aufgrund des potenziellen SQL-Round-Trips während des Halten eines Locks. Wir arbeiten alle hart daran, diese Denkweise im Team zu fördern. Leider schleichen sich manchmal doch noch welche ein, und sie übergehen die Code-Überprüfung. Wir bemerken es nur, wenn das System unter hoher Last steht und alles stockt.

Ich möchte ein Sicherheitsnetz entwickeln und verhindern, dass diese Art von Dingen überhaupt erlaubt ist. Was ich erreichen möchte, ist, dass die Kompilierung fehlschlägt, wenn Cache::fetch von T::update aufgerufen wird.

Es stört mich nicht, wenn es umgangen werden kann. Der Punkt ist, es als Barriere zu haben; der einzige Weg, einen Fehler zu machen, wäre es, es absichtlich zu tun.


Was ich bisher erreicht habe

Ich bin ein wenig vorangekommen, obwohl es nicht ganz das ist, was ich wirklich will. Zum Beispiel möchte ich nicht jeden einzelnen Aufruf von fetch ändern müssen.

template 
class cache_key  {
    cache_key() { }
    friend unsigned Impl::validate();
};

#define CACHE_KEY cache_key::type> ()

Jetzt sieht Cache::fetch so aus:

unsigned fetch (DataType &object, const cache_key &key);

Und eine Implementierung von T könnte so aussehen:

class A_T : public T {
public:
    virtual unsigned validate () {
        global_cache.fetch (object, CACHE_KEY); // OK
    }

    virtual unsigned update () {
        global_cache.fetch (object, CACHE_KEY); // Kann es nicht machen!
    }
};

1voto

Ilya Kobelevskiy Punkte 5175

Ich bin mir nicht bewusst, dass Kompilierungszeitfehlergenerierung existiert, aber es kann gemacht werden, um Laufzeitfehler mit Aktualisierungen nur an der Basisklasse zu generieren.

Ein Weg, dies zu tun, besteht darin, update durch eine nicht virtuelle Proxy-Funktion in einer Basisklasse aufzurufen, die den Status in der Basisklasse setzen würde, um zu erkennen, dass wir uns im Update befinden und daher fetch nicht aufgerufen werden sollte.

class updateWatcher()
{
public:
updateWatcher(bool *valIn) : val(valIn) {*val=true;}
~updateWatcher() {*val=false;}
private:
bool* val;
}

class T {
public:
    // Parallele Aufrufe
    virtual unsigned validate () = 0;

    unsigned updateProxy()
    {
         updateWatcher(&inUpdate); // Ausnahme sicheres Verfolgen, dass wir im Update sind
         return update();
    }

    void
protected:
    // Aufgerufen mit einem gesperrten Lock
    virtual unsigned update () = 0;

    bool inUpdate; // sagt uns, ob wir uns im Update befinden oder nicht
};

class A_T : public T {

public:
    virtual unsigned validate () {
        global_cache.fetch (object,inUpdate); // OK
    }

    virtual unsigned update () {
        global_cache.find (object); // Auch OK

        global_cache.fetch (object,inUpdate); // FEHLER (assert in global_cache.fetch hinzufügen) !
    }
};

Dies wird keine Kompilierung, sondern Laufzeitfehler produzieren, der Vorteil ist, dass es nicht erforderlich ist, Implementierungen zu aktualisieren (außer dem Ersetzen aller global_cache.fetch (...); durch global_cache.fetch (...,inUpdate); und Aufrufe von update() zu updateProxy(); in allen Implementierungen, was effizient automatisiert werden kann). Dann können Sie einige automatisierte Tests als Teil der Build-Umgebung integrieren, um die asserts zu erfassen.

0voto

pepper_chico Punkte 10354

Dies ist nur ein dummes POC, das ich nicht empfehle und möglicherweise nicht Ihren Erwartungen entspricht:

struct T {
    // Parallel aufgerufen
    virtual unsigned validate () = 0;

    // Mit gesperrtem Zugriff aufgerufen
    virtual unsigned update () = 0;
};

struct A_T : T {
    unsigned validate () override;
    unsigned update () override;
};

template 
class Cache {
private:
    class Privileged {
        friend class Cache;

        friend unsigned A_T::validate();

        Privileged( Cache &outer ) : outer(outer) {}

        // Holt das angeforderte Objekt.
        // Wenn es nicht im Speicher vorhanden ist, gehen Sie zu SQL.
        unsigned fetch (DataType &data);

        Cache &outer;
    };

public:
    Privileged privileged { *this };

    // Holt das angeforderte Objekt.
    // Wenn es nicht im Speicher ist, gibt NOT_FOUND zurück.
    unsigned find (DataType &data);
};

Cache global_cache;

unsigned A_T::validate () {
    int object;
    global_cache.privileged.fetch (object); // OK
    return 1;
}

unsigned A_T::update () {
    int object;

    global_cache.find (object); // Auch OK
    global_cache.privileged.fetch (object); // FEHLER!

    return 1;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X