我有以下界面:
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);
};
我想要实现的目标:如果在fetch
期间调用update
,我希望能够编译失败。实际上,我想基于呼叫站点静态禁用该功能。像,
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
。 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!
}
};
答案 0 :(得分:1)
我不知道编译时错误的产生,但可以通过仅对基类进行更新来生成运行时错误。
执行此操作的方法是通过基类中的非虚拟代理函数调用update,该函数会将状态设置为基类以检测我们是否处于更新状态,因此不应调用fetch。
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();在所有实现中,可以高效自动化)。 然后,您可以将一些自动化测试作为构建环境的一部分进行集成,以捕获断言。
答案 1 :(得分:0)
这只是一个愚蠢的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's 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;
}