Могу ли я статически предотвратить одну функцию от вызова другого?

У меня есть следующие интерфейсы:

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