【问题标题】:How do I call all functions from sub-classes when they were defined as pure virtual in the super-class?当子类在超类中定义为纯虚拟时,如何调用子类中的所有函数?
【发布时间】:2011-08-27 15:53:21
【问题描述】:

主要问题是如何实现 startTest() 以便它在所有子类中调用 runTest。谢谢!

/*******************
COMPILER TEST
*******************/

class archeTest
  {
  protected:
    short verbosity_;

  public:

    void setVerbosity(short v)
      {
      if( ((v == 1) || (v == 0) ) 
        {  
        verbosity_ = v;
        }
      else 
        {
        cout << " Verbosity Level Invalid " << endl;
        }
      }

    virtual void runTest() = 0;
      {
      }

    void startTest()
      {
      }
  };

class testNatives : public archeTest 
  {
  public:

    void runTest()
      {
      testInts<short>();
      testInts<int>();
      testInts<long>();
      testInts<unsigned short>();
      testInts<unsigned int>();
      testInts<unsigned long>();
      }      

    void reportResults() const
      {
      }

  protected:

    template<class T> void testFloats()

    template<class T> void testInts()
      {

      verbosity_ = 1;  

      T     failMax;
      short passState;
      short bitDepth;

      const char* a = typeid(T).name();
      bool signedType = ((*a == 't') || (*a == 'j') || (*a == 'm'));

      /* Bit Depth - Algorithm */

      T pow2 = 1, minValue = 0, maxValue = 0, bitCount = 0, failValue = 0;  
      while(pow2 > 0)
        {
        pow2 *= 2;
        maxValue = pow2-1;
        bitCount++;
        }
      failValue = pow2;

      int native1 = bitCount;
      int native2 = sizeof(T)*8;
      int native3 = numeric_limits<T>::digits;  
      if( !signedType )
        {
        native1++;
        native3++;
        }       
      if(verbosity_)
        {
        cout << endl << "**********\n" << reportType(a) << "\n**********" << endl << endl;
        cout << "Bit Depth - Algorithm:\t" << native1 << endl;
        cout << "Bit Depth - Sizeof:\t" << native2 << endl;
        cout << "Bit Depth - File:\t" << native3 << endl;
        }   
        if (native1 == native2 && native1 == native3)
          {
          cout << "Correlation:\t\tPass" << endl ;
          }
        else
          {
          cout << "Correlation:\t\tFail" << endl ;
          }
        cout << "Max Value:\t\t" << maxValue << endl;
        cout << "Max+1 Value:\t\t" << failValue << endl;
      } 

    string reportType(const char* c1)
      { 
      string s1;
      switch(*c1)
        {
        case 't':
          s1 = "Unsigned short";
          break;
        case 'j':
          s1 = "Unsigned int";
          break;
        case 'm':
          s1 = "Unsigned long";
          break;
        case 's':
          s1 = "Short";
          break;
        case 'i':
          s1 = "Int";
          break;
        case 'l':
          s1 = "Long";
          break;
        default:
          s1 = "Switch failed";
        }
      return s1;
      }
  };

int main()
  {
  testNatives A;
  A.runTest();
  } 

【问题讨论】:

  • 请注意,为了运行覆盖方法,您必须有一个具体类的实例(您实际想要运行的那个)。除非您事先知道哪些类将从 archeTest 继承,否则我认为您所要求的是不可能的。如果您确实知道,您可以简单地创建每个实例,并对其进行迭代(如果是这种情况,我将扩展为答案)。

标签: c++ class hierarchy pure-virtual


【解决方案1】:

首先,您将在 archeTest 中像这样声明 startTest。

virtual void startTest() = 0;

这使它成为必须在子类中实现的纯虚函数。要在子类上调用此方法,您必须实例化该特定类的对象。然后您可以通过基类指针或指向子类的指针调用 startTest。请注意,无论哪种情况,指针都必须指向子类的实例化(具体对象)。

【讨论】:

    【解决方案2】:

    编辑:没关系,我的第一个答案没有任何意义。在 C++ 中,你无法获取一个类的所有子类的列表来实例化它们,所以我认为你不能按照你想要的方式实现 runTests()。

    【讨论】:

    • 这不是我们要求的。他要求运行 archeTest 的所有子类,而不是运行 startTest() 的特定实例。
    【解决方案3】:

    这是可能的,但要做到这一点,您必须使用抽象工厂模式。请read this 了解抽象模式是什么以及它如何满足您的需求。

    如果您可以在项目中使用 boost,那么您可以使用 boost::factory template 实现自己的抽象工厂。

    如果您不想推出自己的抽象工厂,您可以使用许多其他实现。 Here 是一个这样的实现的链接。

    编辑:在这种情况下,您还需要一些机制来在编译时向工厂注册新的测试用例。这可以通过利用 c++ 预处理器或模板来实现。 Here 是一种使用模板的方法。

    【讨论】:

    • 我看不出你将如何解决使用 AbstractFactory 知道所有子类列表的问题。这样做只是将问题转移到不同的班级。
    • 不能比我更喜欢你 ;)
    • @J.N.没问题,希望你觉得这篇文章有用:)
    【解决方案4】:

    首先 - 单一责任原则。请记住,您的 archeTest 不应管理所有测试对象。只要有一个(不)著名的Manager 这样做!

    #include <vector>
    
    class TestManager{
      std::vector<archeTest*> _tests;
    public:
      // either
      void AddTest(archeTest* test){
        _tests.push_back(test);
      }
    
      // OR
      archeTest* CreateTest(/*here_be_params*/){
        archeTest* test = new archeTest(/*params*/);
        // do whatever
        _tests.push_back(test);
        return test;        
      }
    
      void RunAllTests() const{
        for(int i=0; i < _tests.size(); ++i)
          _tests[i]->runTests(); 
      }
    
      // if you create tests in here, you also need to release them at the end
      // ONLY do this if your created the tests with CreateTest
      // or if you transfer the ownership of the test pointer to TestManager
      ~TestManager(){
        for(int i=0; i < _tests.size(); ++i)
          delete _tests[i];
      }
    };
    

    运行

    TestManager tmgr;
    // create all your tests, either with
    // archeTest* p = tmgr.CreateTest();
    // OR
    // archeTest* p = new archeTest();
    // tmg.AddTest(p);
    // and then run with
    tmgr.RunAllTests();
    

    再次查看TestManager 实现中的 cmets。


    现在,如果你真的不想要一个额外的课程......这实际上更容易,但它有点代码味道。只需将 archeTest 的构造函数中的类添加到静态链表中即可 - 问题解决了!当然,在销毁时再次删除它。这是因为每个派生类 xxxstructor 都会自动调用基类版本—— *con*structor 在它自己之前, *de*structor 在它自己之后:

    #include <list>
    
    class archeTest{
    private:
      typedef std::list<archeTest*> TestList;
      static TestList _all_tests;
    
      // to erase the right test on destruction
      TestList::iterator _this_test;
    
    public:
      archeTest(){
        _all_tests.push_back(this);
      }
    
      ~archeTest(){
        _all_tests.erase(_this_test);
      }
    
      static void RunAllTests(){
        for(TestList::iterator it = _all_tests.begin(); it != _all_tests.end(); ++it)
          (*it)->runTests();
      }
    };
    
    // in some TestManager.cpp
    #include "TestManager.h"
    
    TestManager::TestList TestManager::_all_tests;
    

    简单地运行它

    // create all your tests;
    // ...
    archeTest::RunAllTests();
    

    因为是静态成员函数,所以不需要实例;

    请注意,我使用了链表,因为这样我可以安全地删除列表中间的测试,而不会使存储在其他测试对象中的引用失效。

    【讨论】:

    • 你在哪里初始化_this_test
    • 如何检测新的子类?他必须显式调用所有子类的构造函数来填充该列表。
    • @user:子类构造函数在自己的构造函数运行之前自动调用基类构造函数。
    • @J.N.:我认为您需要重新阅读 C++ 对象构造以及 @user248808
    • 好的,所以如果我有数千个子类,我必须实例化它们中的每一个。如果我遇到了实例化每个子类的麻烦,为什么我什至应该将对象放在一个列表中并通过循环遍历它,当我可以直接在实例上调用 runTest() 时。请看我的帖子,看看是否有意义。
    【解决方案5】:

    您可能想查看UnitTest++(您可以browse the source code here)。特别注意TestMacros.hCheckMacros.h,顾名思义,它们实现了宏来自动收集和运行测试。

    例如,以下代码是“通过 UnitTest++ 运行失败测试的最小 C++ 程序。”

    // test.cpp
    #include <UnitTest++.h>
    
    TEST(FailSpectacularly)
    {
        CHECK(false);
    }
    
    int main()
    {
        return UnitTest::RunAllTests();
    }
    

    您可以在(简要)UnitTest++ overview 上阅读更多内容。我没有深入研究TESTCHECK 宏是如何实现的细节,但它们确实允许您在许多不同的CPP 文件中声明测试,然后通过对UnitTest::RunAllTests() 的一次调用来运行它们。也许这已经足够接近您想要做的事情了?

    【讨论】:

    • 还有很多其他的套件,比如 UnitTest++,看看 Boost Test 和 Google Test 套件。
    【解决方案6】:

    因为很多人发现很难阅读我列出的所有帖子。我使用 boost 实现了这个版本。

    诀窍在于,当使用派生类定义(新测试用例)扩展 TestTemplate 的定义时,由于静态常量的初始化,它会强制调用 TestManager::Register 方法。

    template<typename TestCase> 
    const unsigned Test<TestCase>::m_uTestID = TestManager::Register(boost::factory<TestCase*>());
    

    这确保派生类的构造函数的函子存储在 TestManager 的映射中。现在在 TestManager 中,我只需遍历映射并使用仿函数实例化每个 Testcase 并在新创建的实例上调用 run 方法。

    任何从TestTemplate派生的类都会自动注册,无需手动全部列出。

    #include <map>
    #include <iostream>
    #include <boost/function.hpp>
    #include <boost/functional/factory.hpp>
    
    class ITest {
    public:
        virtual void run() {
            runTest();
        }
        virtual void runTest() = 0;
    };
    
    
    typedef boost::function< ITest* ()> TestFactory;
    
    class TestManager {
    public:
        static int Register(TestFactory theFactory) {
        std::cout<<"Registering Test Case"<<std::endl;
            m_NumTests++;
            m_mapAllTests[m_NumTests] = theFactory;
            return m_NumTests;
        }
    
      void run() {
            for(unsigned uTestID = 1; uTestID <= m_NumTests; uTestID++) {
                ITest* theTestCase = m_mapAllTests[uTestID]();
                theTestCase->run();
                delete theTestCase;
            }
        }
    
    private:
        static unsigned m_NumTests;
        static std::map<unsigned,  TestFactory> m_mapAllTests;
    };
    
    unsigned TestManager::m_NumTests = 0;
    std::map<unsigned,  TestFactory> TestManager::m_mapAllTests;
    
    
    template<typename TestCase>
    class Test : public ITest {
    public:
      unsigned getID() const {
        return m_uTestID;
      }
    private:
        static const unsigned m_uTestID;
    };
    template<typename TestCase> 
    const unsigned Test<TestCase>::m_uTestID = TestManager::Register(boost::factory<TestCase*>());
    
    
    
    class Test1 : public Test<Test1> {
    public:
        virtual void runTest() {
        std::cout<<"Test Id:"<<getID()<<std::endl;
        }
    };
    
    class Test2 : public Test<Test2> {
    public:
        virtual void runTest() {
        std::cout<<"Test Id:"<<getID()<<std::endl;
    
        }
    };
    
    
    class Test3 : public Test<Test3> {
    public:
        virtual void runTest() {
        std::cout<<"Test Id:"<<getID()<<std::endl;
        }
    };
    
    class Test4 : public Test<Test4> {
    public:
        virtual void runTest() {
        std::cout<<"Test Id:"<<getID()<<std::endl;
        }
    };
    
    int main() {
        TestManager theManager;
        theManager.run();
    }
    

    我确实在 VS05 和 VS10 上测试了该解决方案。以下是预期的输出。

    Registering Test Case
    Registering Test Case
    Registering Test Case
    Registering Test Case
    Test Id:1
    Test Id:2
    Test Id:3
    Test Id:4
    

    希望它能消除一些困惑。

    【讨论】:

    • +1 用于使用模板而不是宏的简洁解决方案
    • 您应该已经编辑了您的答案。 :) 不过,这真的很聪明,谢谢!
    • @Xeo,谢谢!下次我将更新现有帖子而不是添加新帖子。
    猜你喜欢
    • 2011-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多