У меня есть следующие интерфейсы:
class T {
public:
// Called in parallel
virtual unsigned validate () = 0;
// Called with a lock taken out
virtual unsigned update () = 0;
};
template <typename DataType>
class Cache {
public:
// Gets the requested object.
// If it doesn't exist in memory, go to SQL.
unsigned fetch (DataType &data);
// Gets the requested object.
// If it not in memory, returns NOT_FOUND.
unsigned find (DataType &data);
};
То, что я хотел бы достичь: Я хотел бы иметь возможность скомпилировать компиляцию, если во время update
вызывается fetch
. Фактически, я хотел бы отключить функцию статически, основываясь на сайте вызова. Что-то вроде,
std::enable_if <callsite_is_not_implementation_of_T_update, unsigned>
fetch (DataType &data);
Использование будет работать примерно так:
class A_T : public T {
public:
virtual unsigned validate () {
global_cache.fetch (object); // OK
}
virtual unsigned update () {
global_cache.find (object); // Also OK
global_cache.fetch (object); // FAIL!
}
};
Фон
В моем проекте около 500 реализаций T
.
Приложение циклами во многих потоках и вызывает validate
для многих экземпляров T
параллельно. Затем вынимается глобальная блокировка и вызывается update
. Следовательно, скорость update
имеет решающее значение. Общее отношение состоит в том, чтобы взять время, которое вам нужно во время validate
, но update
должно быть как можно более плоским.
Моя проблема заключается в использовании Cache
. A Cache
- это в основном кеш-память в памяти объектов данных из SQL.
Политика заключается в том, чтобы никогда не вызывать Cache::fetch
во время update
из-за потенциального двойного отключения SQL, удерживая блокировку. Мы все работаем над тем, чтобы способствовать этому мышлению внутри команды. К сожалению, некоторые из них все еще скрываются и проходят проверку кода. Мы замечаем их только тогда, когда система находится под большой нагрузкой, и все останавливается.
Я хотел бы создать страховочную сетку и вообще не допускать такого рода вещей. Я бы хотел добиться компиляции, если Cache::fetch
вызывается из T::update
.
Я не против, если его можно обойти. Дело в том, чтобы иметь это как барьер; единственный способ сделать ошибку - намеренно сделать это.
Что у меня было до сих пор
Я немного поработал с этим, хотя не совсем то, что я действительно переживаю. Например, я бы предпочел не менять каждый вызов на fetch
.
template <typename Impl>
class cache_key {
cache_key() { }
friend unsigned Impl::validate();
};
#define CACHE_KEY cache_key<std::remove_pointer<decltype(this)>::type> ()
Итак, теперь Cache::fetch
выглядит следующим образом:
unsigned fetch (DataType &object, const cache_key &key);
И реализация T
может выглядеть так:
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); // Can't do it!
}
};