Могу ли я статически предотвратить одну функцию от вызова другого?
У меня есть следующие интерфейсы:
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!
}
};
Ответы
Ответ 1
Мне не известно о генерации времени компиляции, но это можно сделать для генерации ошибки во время выполнения с обновлениями только базового класса.
Способ сделать это - вызвать обновление через не виртуальную прокси-функцию в базовом классе, который установил бы состояние в базовый класс, чтобы обнаружить, что мы обновляемся, и поэтому выбор не должен вызываться.
class updateWatcher()
{
public:
updateWatcher(bool *valIn) : val(valIn) {*val=true;}
~updateWatcher() {*val=false;}
private:
bool* val;
}
class T {
public:
// Called in parallel
virtual unsigned validate () = 0;
unsigned updateProxy()
{
updateWatcher(&inUpdate); //exception safe tracker we are in update
return update();
}
void
protected:
// Called with a lock taken out
virtual unsigned update () = 0;
bool inUpdate; // tells if we are in update or not
};
class A_T : public T {
public:
virtual unsigned validate () {
global_cache.fetch (object,inUpdate); // OK
}
virtual unsigned update () {
global_cache.find (object); // Also OK
global_cache.fetch (object,inUpdate); // FAIL (put assert in global_cache.fetch) !
}
};
Это не приведет к компиляции, но ошибка времени выполнения, преимущество в том, что нет необходимости обновлять какие-либо реализации (за исключением замены всего global_cache.fetch(...); global_cache.fetch(..., inUpdate); и вызывает update() для updateProxy(), во всех реализациях, которые могут быть автоматизированы эффективно).
Затем вы можете интегрировать некоторые автоматические тесты как часть среды сборки, чтобы поймать утверждения.
Ответ 2
Это просто глупый POC, который я не рекомендую, и не могу оправдать ваши ожидания:
struct T {
// Called in parallel
virtual unsigned validate () = 0;
// Called with a lock taken out
virtual unsigned update () = 0;
};
struct A_T : T {
unsigned validate () override;
unsigned update () override;
};
template <typename DataType>
class Cache {
private:
class Privileged {
friend class Cache<DataType>;
friend unsigned A_T::validate();
Privileged( Cache<DataType> &outer ) : outer(outer) {}
// Gets the requested object.
// If it doesn't exist in memory, go to SQL.
unsigned fetch (DataType &data);
Cache<DataType> &outer;
};
public:
Privileged privileged { *this };
// Gets the requested object.
// If it not in memory, returns NOT_FOUND.
unsigned find (DataType &data);
};
Cache<int> 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); // Also OK
global_cache.privileged.fetch (object); // FAIL!
return 1;
}