更新时间:2023-11-09 20:14:34
这是我最小的侵入版本没有SFINAE。用Apple LLVM 5.1和g ++ 4.8.2测试。这只是代码下面描述的一般想法的一个实现。
#include< cstdio>
//全局系统函数进行测试;通常在实际测试中像`fopen`一样
const char * GetString(){returnGLOBAL; }
//列出所有可能被函数指针覆盖的全局函数
//(用原始函数初始化它们)并提供模板覆盖函数。
struct PossibleOverridesList
{
decltype(& :: GetString)GetString =& :: GetString;
template< typename O>
void setOverrides(){
O :: setMyOverrides(this);
};
};
//不提供被测试的标准系统函数的覆盖
//(setOverrides方法不做任何操作)
struct NoOverrides {static void setMyOverrides(PossibleOverridesList * ol){}; };
//覆盖正在测试的系统函数的函数集
//(setOverrides方法设置指向静态成员函数的所需指针)
struct TestOverrides {
/ /如果这是`fopen`这可能是一个版本总是失败
static const char * GetString(){returnOVERRIDE; }
static void setMyOverrides(PossibleOverridesList * ol){ol-> GetString =& GetString; };
};
//测试用例(继承不依赖于模板参数,所以它包含在查找中)
struct Test:PossibleOverridesList {
void Run(){
printf(%s\\\
,GetString());
}
};
int main(){
//测试没有覆盖;使用系统函数
Test test1;
test1.setOverrides< NoOverrides>();
test1.Run();
//使用覆盖测试;使用测试用例版本的系统函数
Test test2;
test2.setOverrides< TestOverrides>();
test2.Run();
}
我的想法是下面这样:因为我的理解是你不想更改 Run()
中的代码,没有办法从模板类中查找未修改的名称。因此,在查找 GetString
时,一些(可调用)占位符对象必须在作用域中,我们仍然可以决定调用哪个函数(如果 GetString ()
在里面运行一次绑定到全局
GetString()
做任何事情以后改变它)。这可以是周围命名空间中的函数或(非模板)基类中的函数/函数对象/函数指针。我在这里选择后者(使用 PossibleOverridesList
)以尽可能接近原始代码。默认情况下,此占位符调用函数的原始版本,但可以通过类似于覆盖类的类进行修改。唯一的区别是这些覆盖类需要额外的方法 setMyOverrides(PossibleOverridesList *)
相应地设置占位符类中的函数指针。
main中的更改不是绝对必要的。在我的代码中,你必须调用 setOverrides< ... OverriderClass ...>()
,而不是在声明中指定OverriderClass。但是应该很容易编写一个从Test继承的包装器模板类,并通过在构建过程中使用其自身的模板参数内部调用 setOverrides
方法来保留原始语法。 p>
上面的代码比在 NoOverrides
类中提供包装器的解决方案具有几个优点(你的建议是你的问题):
PossibleOverridesList
。很容易发现,如果你忘记了一个,因为试图在相应的Override类的 setMyOverrides
方法中设置该成员将无法编译。因此,如果在任何覆盖类中添加另一个覆盖方法,只有两个地方可以更改。 Run()
中的代码即可使用继承类中的函数。 setOverrides()
方法来连续替换某些函数。当然也有局限性,例如,如果原来的系统函数不在 ::
命名空间,但也许他们不是更多严格比包装解决方案。正如我上面所说,可能有非常不同的实现使用相同的概念,但我认为有一些形式的默认占位符的重写方法是不可避免的(虽然我很想看到没有它的解决方案)。
编辑:
我刚刚想出了一个更少的侵入版本,只需要额外的 PossibleOverridesList
类,但没有更改 Run()
, main()
。 最重要的是,不需要 setOverrides
方法和 Test< Override>
原始代码保留!
这里的诀窍是使用虚拟方法而不是静态方法,并利用虚拟继承。这样,在 Run()
中的 GetString()
的函数调用可以绑定到 PossibleOverridesList
(因为继承不依赖于模板参数)。然后在运行时,调用被分派到最导出的类,即在重载类中。这只是明确的,因为虚拟继承,所以,事实上,只有一个 PossibleOverridesList
对象存在于类中。
因此,总之,此版本的最小变化是:
PossibleOverridesList $ c
更改重载类中的成员方法静态
到虚拟
。 Test
类实际上继承了PossibleOverridesList以及任何其他继承。 NoOverride
它不是绝对必要的,但对于一致的模式很好。这里的代码(再次,用苹果LLVM 5.1和g ++ 4.8.2测试):
#include< cstdio&
//全局系统函数进行测试;通常在实际测试中像`fopen`一样
const char * GetString(){returnGLOBAL; }
//列出所有可能被函数指针覆盖的全局函数
struct PossibleOverridesList
{
virtual const char * GetString(){return :: GetString ();};
};
//不提供正在测试的标准系统函数的覆盖
struct NoOverrides:virtual PossibleOverridesList {};
//覆盖正在测试的系统函数的函数集
struct TestOverrides:virtual PossibleOverridesList {
//如果这是fopen,这可能是一个总是失败的版本
virtual const char * GetString(){returnOVERRIDE; }
};
//测试用例(从第一个类继承不依赖于模板参数,所以它包含在查找中)
template< typename Override>
struct Test:virtual PossibleOverridesList,Override {
void Run(){
printf(%s\\\
,GetString());
}
};
int main(){
//测试没有覆盖;使用系统函数
Test< NoOverrides> test1;
test1.Run();
//使用覆盖测试;使用测试用例版本的系统函数
Test< TestOverrides> test2;
test2.Run();
}
There's a pattern in some legacy test framework code that depends on Visual C++'s broken two-phase look up, causing headaches when porting to other compilers. There are numerous solutions that I know of to fix the issue but all require "extensive" structural changes.
While I'm fairly certain that there isn't, I am curious if there may be an "easy" hack that gets the desired behavior in standards-compliant compilers with a very small set of required changes.
The pattern is seen in this example:
#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }
// provides no overrides of the standard system functions being tested
struct NoOverrides {};
// set of functions overriding the system functions being tested
struct TestOverrides {
// if this were `fopen` this might be a version that always fails
static const char* GetString() { return "OVERRIDE"; }
};
// test case
template <typename Overrides>
struct Test : private Overrides {
void Run() {
// call to GetString is not dependent on Overrides
printf("%s\n", GetString());
}
};
int main() {
// test with no overrides; use the system functions
Test<NoOverrides> test1;
test1.Run();
// test with overrides; use test case version of system functions
Test<TestOverrides> test2;
test2.Run();
}
The idea is that there are global functions, usually something defined in a system header (such as an ANSI C function or OS-provided function). There is then a type that defines a bunch of alternate versions of this as static member functions. A test can inherit either from the type that has these alternate versions or a type that has no alternatives.
With Visual C++'s broken two-phase lookup, the unqualified calls to the system functions being tested are delayed until template instantiation. If the overrides type TestOverrides
is a base type of the Test
type, then the static member version of GetString
is found. With other compilers that properly implement two-phase lookup, the free function version is found during initial parsing and is already resolved by the time the template is instantiated.
I'm well aware of some relatively intrusive solutions to this problem. One would be to make the NoOverrides
type actually have wrappers that call the free functions and then make the call to GetString
qualified to the Overrides
template parameter, which is my first instinct. Example:
#include <cstdio>
const char* GetString() { return "GLOBAL"; }
// wrappers to invoke the system function versions
struct NoOverrides {
static const char* GetString() { return ::GetString(); }
};
struct TestOverrides {
static const char* GetString() { return "OVERRIDE"; }
};
template <typename Overrides>
struct Test {
void Run() {
// call to GetString is made dependent on template type Overrides
printf("%s\n", Overrides::GetString());
}
};
int main() {
// test with the default overrides; use the system functions
Test<NoOverrides> test1;
test1.Run();
Test<TestOverrides> test2;
test2.Run();
}
Clearly there are working solutions to deal with two-phase lookup. A good number of these tests can be fairly complex and would take a lot of work to convert to use a structure like the one I just provided. I'm curious if there is another solution that requires less structural changes to the code that I'm not thinking of.
Here's my least intrusive version without SFINAE. Tested with Apple LLVM 5.1 and g++ 4.8.2. This is only one realization of the general idea described below the code.
#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }
// list all global functions that could possibly be overridden by function pointers
// (initialize them with the original function) and provide template overriding function.
struct PossibleOverridesList
{
decltype(&::GetString) GetString = &::GetString;
template<typename O>
void setOverrides() {
O::setMyOverrides(this);
};
};
// provides no overrides of the standard system functions being tested
// (setOverrides method does nothing)
struct NoOverrides { static void setMyOverrides(PossibleOverridesList* ol){}; };
// set of functions overriding the system functions being tested
// (setOverrides method sets the desired pointers to the static member functions)
struct TestOverrides {
// if this were `fopen` this might be a version that always fails
static const char* GetString() { return "OVERRIDE"; }
static void setMyOverrides(PossibleOverridesList* ol) { ol->GetString = &GetString; };
};
// test case (inheritance doesn't depend on template parameters, so it gets included in the lookup)
struct Test : PossibleOverridesList {
void Run() {
printf("%s\n", GetString());
}
};
int main() {
// test with no overrides; use the system functions
Test test1;
test1.setOverrides<NoOverrides>();
test1.Run();
// test with overrides; use test case version of system functions
Test test2;
test2.setOverrides<TestOverrides>();
test2.Run();
}
My idea is the following: Since my understanding is that you do not want to change the code inside Run()
, there is no way to look up the unmodified name from a template class. Therefore, some (callable) placeholder object must be in scope when GetString
is looked up, inside which we can still decide which function to call (if GetString()
inside Run()
binds to the global GetString()
once, we cannot do anything later to change it). This could be a function in the surrounding namespace or a function/function object/function pointer in a (non-template) base class. I chose the latter here (with the class PossibleOverridesList
) to stay as close to your original code as possible. This placeholder by default calls the original version of the function but can be modified by classes similar to your Override classes. The only difference is that those Override classes need the additional method setMyOverrides(PossibleOverridesList*)
which sets the function pointers in the placeholder class accordingly.
The changes in main are not strictly necessary. In my code, you have to call setOverrides<...OverriderClass...>()
instead of specifying the OverriderClass in the declaration. But it should be easy to write a wrapper template class that inherits from Test and preserves the original syntax by internally calling the setOverrides
method with its template argument on itself during construction.
The code above has several advantages over the solution of providing wrappers in the NoOverrides
class (your suggestion the your question):
PossibleOverridesList
. It's easy to spot if you forgot one, because trying to set that member in the setMyOverrides
method of the corresponding Override class will not compile. So there are only two places to change something if you add another overridden method in any Override class.Run()
to use the functions from the inherited class.setOverrides()
method multiple times on the Test object to replace certain functions successively.Of course there are also limitations, for example if the original system function is not in the ::
namespace, but maybe they are not more strict than for the wrapper solution. As I said above, there are probably very different implementations that use the same concept, but I think having some form of default placeholder for the overridden methods is inevitable (although I would love to see a solution without it).
EDIT:
I just came up with an even less intrusive version that only needs the additional PossibleOverridesList
class, but no changes to Run()
, main()
. Most importantly, there is no need for the setOverrides
methods and the Test<Override>
templated inheritance from the original code is preserved!
The trick here is to use virtual methods instead of static ones and exploit virtual inheritance. This way, the function call to GetString()
in Run()
can bind to the virtual function in PossibleOverridesList
(because that inheritance does not depend on the template parameter). Then at run time, the call is dispatched to the most derived class, i.e. in the override class. This is only unambiguous because of the virtual inheritance, so, in fact, only one PossibleOverridesList
object is present in the class.
So, in summary, the minimal changes in this version are:
PossibleOverridesList
with all the functions that might be overridden (as virtual member functions), redirecting to the original system function.static
to virtual
.Test
classes inherit virtually from PossibleOverridesList in addition to any other inheritance. For NoOverride
it is not strictly necessary, but nice for a consistent pattern.Here's the code (again, tested with Apple LLVM 5.1 and g++ 4.8.2):
#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }
// list all global functions that could possibly be overridden by function pointers
struct PossibleOverridesList
{
virtual const char* GetString() {return ::GetString();};
};
// provides no overrides of the standard system functions being tested
struct NoOverrides : virtual PossibleOverridesList { };
// set of functions overriding the system functions being tested
struct TestOverrides : virtual PossibleOverridesList {
// if this were `fopen` this might be a version that always fails
virtual const char* GetString() { return "OVERRIDE"; }
};
// test case (inheritance from first class doesn't depend on template parameter, so it gets included in the lookup)
template <typename Override>
struct Test : virtual PossibleOverridesList, Override {
void Run() {
printf("%s\n", GetString());
}
};
int main() {
// test with no overrides; use the system functions
Test<NoOverrides> test1;
test1.Run();
// test with overrides; use test case version of system functions
Test<TestOverrides> test2;
test2.Run();
}