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

时间:2014-02-11 20:04:46

标签: 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();
}

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

使用Visual C ++中断的两阶段查找,对正在测试的系统函数的非限定调用将延迟到模板实例化。如果覆盖类型TestOverridesTest类型的基本类型,则会找到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();
}

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

1 个答案:

答案 0 :(得分: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()中的代码,因此无法从模板类中查找未修改的名称。因此,当查找GetString时,某些(可调用的)占位符对象必须在范围内,我们仍然可以在其中决定调用哪个函数(如果GetString()内的Run()绑定到全局{ {1}}曾经,我们以后什么也做不了改变它。这可以是周围命名空间中的函数,也可以是(非模板)基类中的函数/函数对象/函数指针。我在这里选择后者(使用类GetString())尽可能接近原始代码。默认情况下,此占位符调用函数的原始版本,但可以通过类似于Override类的类进行修改。唯一的区别是那些Override类需要额外的方法PossibleOverridesList,它相应地设置占位符类中的函数指针。

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

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

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

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


编辑: 我刚刚提出了一个更少侵入性的版本,只需要额外的::类,但不需要更改PossibleOverridesListRun()最重要的是,不需要main()方法,并保留原始代码的setOverrides模板化继承

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

因此,总而言之,此版本的最小变化是:

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

这里是代码(再次,使用Apple LLVM 5.1和g ++ 4.8.2进行测试):

NoOverride