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