强制不合格的名称是依赖值

Sea*_*tch 14 c++

某些遗留测试框架代码中存在一种模式,它依赖于Visual C++的两阶段查找,在移植到其他编译器时会引起麻烦.我知道有许多解决方案来解决这个问题,但都需要"广泛的"结构变化.

虽然我很确定没有,但我很好奇是否存在一个"简单"的黑客攻击,它可以在符合标准的编译器中获得所需的行为,并且只需要一小组所需的更改.

该示例中显示了该模式:

#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();
}
Run Code Online (Sandbox Code Playgroud)

这个想法是有全局函数,通常是在系统头中定义的东西(例如ANSI C函数或OS提供的函数).然后有一种类型,它将一组备用版本定义为静态成员函数.测试可以从具有这些备用版本的类型继承,也可以从没有替代版本的类型继承.

使用Visual C++的两阶段查找,对被测试的系统函数的非限定调用将被延迟,直到模板实例化为止.如果覆盖类型TestOverrides是该类型的基类型Test,则GetString找到静态成员版本.对于正确实现两阶段查找的其他编译器,在初始解析期间找到自由函数版本,并且在模板实例化时已经解析.

我很清楚这个问题的一些相对侵入性的解决方案.一种方法是使NoOverrides类型实际上具有调用自由函数的包装器,然后调用GetString限定Overrides模板参数,这是我的第一直觉.例:

#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();
}
Run Code Online (Sandbox Code Playgroud)

显然,有一些工作解决方案可以处理两阶段查找.大量的这些测试可能相当复杂,需要大量工作才能转换为使用我刚刚提供的结构.我很好奇是否有另一种解决方案需要对我没有想到的代码进行较少的结构更改.

Ogu*_*guk 2

这是我没有使用 SFINAE 的干扰最小的版本。使用 Apple LLVM 5.1 和 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
// (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();
}
Run Code Online (Sandbox Code Playgroud)

我的想法如下:由于我的理解是您不想更改里面的代码Run(),因此无法从模板类中查找未修改的名称。因此,某些(可调用的)占位符对象在查找时必须在作用域内GetString,在该对象内我们仍然可以决定调用哪个函数(如果GetString()insideRun()绑定到全局GetString()一次,我们以后就无法做任何事情来更改它)。这可以是周围命名空间中的函数或(非模板)基类中的函数/函数对象/函数指针。我在这里选择了后者(带有 class PossibleOverridesList),以尽可能接近您的原始代码。默认情况下,此占位符调用函数的原始版本,但可以通过与 Override 类类似的类进行修改。唯一的区别是这些 Override 类需要额外的方法setMyOverrides(PossibleOverridesList*)来相应地设置占位符类中的函数指针。

main 中的更改并不是绝对必要的。在我的代码中,您必须调用setOverrides<...OverriderClass...>()而不是在声明中指定 OverriderClass 。但是,编写一个继承自 Test 的包装模板类应该很容易,并通过setOverrides在构造期间内部调用带有其模板参数的方法来保留原始语法。

与在类中提供包装器的解决方案相比,上面的代码有几个优点NoOverrides(您的建议就是您的问题):

  • 使用包装器,如果您有许多具有许多重写函数的 Override 类,则必须始终为您不想重写的所有函数提供包装器。这里,只有一个可能被覆盖的全局函数列表,PossibleOverridesList. 如果您忘记了,很容易发现,因为尝试在setMyOverrides相应 Override 类的方法中设置该成员将无法编译。因此,如果您在任何 Override 类中添加另一个重写方法,则只有两个地方可以更改某些内容。
  • 您不需要为系统函数的包装器重现函数签名
  • 您不必更改内部代码Run()即可使用继承类中的函数。
  • 我不知道你原来的 VC++ Test 复杂代码是否可以实际继承多个 Override 类。但如果可以,包装解决方案将不起作用,因为您不知道如何限定该方法(您不知道它来自哪个继承类)。setOverrides()在这里,您可以轻松地在 Test 对象上多次调用该方法来连续替换某些函数。

当然也有限制,例如,如果原始系统函数不在::命名空间中,但也许它们并不比包装器解决方案更严格。正如我上面所说,可能有非常不同的实现使用相同的概念,但我认为为重写的方法提供某种形式的默认占位符是不可避免的(尽管我希望看到没有它的解决方案)。


编辑:我刚刚想出了一个侵入性更小的版本,只需要额外的类,但没有对,PossibleOverridesList进行任何更改。最重要的是,不需要方法,并且保留了原始代码的模板化继承!Run()main()setOverridesTest<Override>

这里的技巧是使用虚拟方法而不是静态方法并利用虚拟继承。这样,对GetString()in 的函数调用Run()就可以绑定到虚函数 in PossibleOverridesList(因为继承不依赖于模板参数)。然后在运行时,调用被分派到最底层的派生类,即重写类中。这只是由于虚拟继承而明确的,因此,事实上,PossibleOverridesList类中只存在一个对象。

所以,总而言之,这个版本的最小变化是:

  • 定义PossibleOverridesList所有可能被覆盖的函数(作为虚拟成员函数),重定向到原始系统函数。
  • 将重写类中的成员方法从 更改static为virtual。
  • Test除了任何其他继承之外,让所有重写和类实际上继承自PossibleOverridesList。因为NoOverride这并不是绝对必要的,但对于一致的模式来说很好。

这是代码(再次使用 Apple LLVM 5.1 和 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();
}
Run Code Online (Sandbox Code Playgroud)