一些旧式测试框架代码中有一种模式取决于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();
}

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

最佳答案

这是我在没有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()一次绑定(bind)至全局GetString(),我们以后将无法进行任何更改)。这可以是周围 namespace 中的函数,也可以是(非模板)基类中的函数/函数对象/函数指针。我在这里选择了后者(使用PossibleOverridesList类),以使其尽可能接近您的原始代码。默认情况下,此占位符调用函数的原始版本,但可以通过类似于Override类的类来对其进行修改。唯一的区别是那些Override类需要附加的方法setMyOverrides(PossibleOverridesList*),该方法相应地在占位符类中设置函数指针。

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

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

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

    编辑:
    我只是想出了一个更具侵入性的版本,只需要附加的PossibleOverridesList类,而无需更改Run()main()最重要的是,不需要setOverrides方法,并且保留了原始代码中Test<Override>模板化的继承继承!

    这里的技巧是使用虚拟方法而不是静态方法并利用虚拟继承。这样,对GetString()中的Run()的函数调用可以绑定(bind)到PossibleOverridesList中的虚拟函数(因为继承不取决于template参数)。然后在运行时,将 call 调度到派生程度最高的类,即覆盖类中。由于虚拟继承,这仅是明确的,因此,实际上,该类中仅存在一个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();
    }
    

    关于c++ - 强制将不合格的名称作为从属值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21711871/

    10-10 23:16