Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I statically prevent one function from calling another?

Tags:

c++

I have the following interfaces:

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's not in memory, returns NOT_FOUND.
    unsigned find (DataType &data);
};

What I'd like to achieve: I would like to be able to have compilation fail if fetch is called during update. Effectively, I'd like to disable the function statically, based on the call site. Something like,

std::enable_if <callsite_is_not_implementation_of_T_update, unsigned> 
fetch (DataType &data);

Usage would work something like this:

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


Background

There are approximately 500 implementations of T in my project.

The application loops in many threads and calls validate for many instances of T in parallel. Then a global lock is taken out, and update is called. Hence, the speed of update is crucial. The general attitude is to take whatever time you need during validate, but update should be as lean as possible.

My problem is with the use ofCache. A Cache is basically an in-memory cache of data objects from SQL.

The policy is to never call Cache::fetch during update because of the potential SQL round-trip while holding a lock. We're all working hard to foster this mindset within the team. Unfortunately, some of these still sneak in, and get past code review. We only notice them when the system is under heavy load and everything grinds to a halt.

I'd like to develop a safety net and prevent this kind of thing from being allowed at all. What I'd like to achieve is to have compilation fail if Cache::fetch is called from T::update.

I don't mind if it can be worked around. The point is to have it as a barrier; the only way you could make a mistake is to intentionally do it.


What I've Got So Far

I've made it a little bit of the way there, although not quite what I'm really after. For instance, I'd prefer not to have to change every single call to fetch.

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

#define CACHE_KEY cache_key<std::remove_pointer<decltype(this)>::type> ()

So now Cache::fetch looks like:

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

And an implementation of T might look like this:

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!
    }
};
like image 610
Anthony Avatar asked Jan 03 '14 03:01

Anthony


People also ask

How do you stop function calling?

Complete HTML/CSS Course 2022 To stop the execution of a function in JavaScript, use the clearTimeout() method. This function call clears any timer set by the setTimeout() functions.

Can static methods call each other?

A static method can call only other static methods; it cannot call a non-static method. A static method can be called directly from the class, without having to create an instance of the class. A static method can only access static variables; it cannot access instance variables.

How do you stop a function from calling in Python?

exit() where you want to stop.

Can functions make calls to other functions within the same program?

It is important to understand that each of the functions we write can be used and called from other functions we write. This is one of the most important ways that computer scientists take a large problem and break it down into a group of smaller problems.


1 Answers

I'm not aware of compilation time error generation, but it can be done to generate run-time error with updates only to base class.

A way to do it is to call update through a non-virtual proxy function in a base class, which would set state to the base class to detect that we are in update and therefore fetch should not be called.

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

This will not produce compilation but run-time error, benefit is that there is no need to update any implementations (except replacing all global_cache.fetch (...); by global_cache.fetch (...,inUpdate); and calls to update() to updateProxy(); in all implementations, which can be automated efficiently). You can then integrate some automated tests as part of the build environment to catch the asserts.

like image 72
Ilya Kobelevskiy Avatar answered Oct 05 '22 21:10

Ilya Kobelevskiy