【问题标题】:How to simulate virtuality for method template如何模拟方法模板的虚拟性
【发布时间】:2025-12-21 05:25:06
【问题描述】:

我有一个类层次结构,我想在其中引入一个方法模板,它的行为就像它是虚拟的一样。例如一个简单的层次结构:

class A {
  virtual ~A() {}

  template<typename T>
  void method(T &t) {}
};

class B : public A {
  template<typename T>
  void method(T &t) {}
};

然后我创建对象 B:

A *a = new B();

我知道我可以通过typeid(a) 获取存储在a 中的类型。当我知道类型时,如何动态调用正确的B::method?我可能有这样的情况:

if(typeid(*a)==typeid(B))
    static_cast<B*>(a)->method(params);

但我想避免出现这样的情况。我正在考虑创建一个以typeid 为键的std::map,但我会设置什么值?

【问题讨论】:

    标签: c++ templates polymorphism


    【解决方案1】:

    你可能知道,你不能有虚函数的模板,因为整个虚函数都是类类型的一部分,必须事先知道。这排除了任何简单的“任意覆盖”。

    如果可以选择,您可以将模板参数作为类的一部分:

    template <typename T> class A
    {
    protected:
      virtual void method(T &);
    };
    
    template <typename T> class B : public A<T>
    {
      virtual void method(T &); // overrides
    };
    

    更复杂的方法可能会使用一些调度程序对象:

    struct BaseDispatcher
    {
      virtual ~BaseDispatcher() { }
      template <typename T> void call(T & t) { dynamic_cast<void*>(this)->method(t); }
    };
    struct ConcreteDispatcher : BaseDispatcher
    {
      template <typename T> void method(T &);
    };
    
    class A
    {
    public:
      explicit A(BaseDispatcher * p = 0) : p_disp(p == 0 ? new BaseDispatcher : p) { }
      virtual ~A() { delete p_disp; };
    private:
      BaseDispatcher * p_disp;
      template <typename T> void method(T & t) { p_disp->call(t); }
    };
    
    class B : public A
    {
    public:
      B() : A(new ConcreteDispatcher) { }
      // ...
    };
    

    【讨论】:

    • 我做不到。我真的需要一个方法模板。
    • @JurajBlaho:我添加了另一个想法。
    • @JurajBlaho:嗯,making member templates virtual just doesn't work in C++,所以你将不得不重新考虑你的方法并对语言的限制做出一些让步。
    • 其实,没关系,第二个想法行不通。你仍然需要某种多态性,它不能被模板化。
    【解决方案2】:

    有没有可以提取和虚拟化的通用代码?

    class A {
      virtual ~A() {}
    
      template<typename T>
      void method(T &t) 
      {
          ...
          DoSomeWork();
          ...
      }
    
      virtual void DoSomeWork() {}
    };
    
    class B : public A {
      virtual void DoSomeWork() {}
    };
    

    【讨论】:

      【解决方案3】:

      您可以使用“Curiously Recurring Template Pattern” http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

      使用这种模式,基类将派生类类型作为模板参数,这意味着基类可以将自己转换为派生类型,以便调用派生类中的函数。它是一种虚拟函数的编译时实现,另外还有一个好处是不必进行虚拟函数调用。

      template<typename DERIVED_TYPE>
      class A {
      public:
          virtual ~A() {}
      
          template<typename T>
          void method(T &t) { static_cast<DERIVED_TYPE &>(*this).methodImpl<T>(t); }
      };
      
      class B : public A<B>
      {
      friend class A<B>;
      
      public:
          virtual ~B() {}
      
      private:
          template<typename T>
          void methodImpl(T &t) {}
      };
      

      然后就可以这样使用了……

      int one = 1;
      A<B> *a = new B();
      a->method(one);
      

      【讨论】:

      • 在这种情况下将没有基类,因此您不能存储这些类。在向量中,因为 A 类是模板。但是您可以从一个通用的 ABase 继承,但在这种情况下,您无法从 Base 访问该方法。
      • @Industrial-antidepressant 是的,这种方法显然有缺点,例如无法在容器中存储不同的派生类型,但这仍然是一个有用的设计模式。
      【解决方案4】:

      【讨论】:

        【解决方案5】:

        糟糕。最初回答at the wrong question - 嗯,在另一个问题上

        经过一番思考,我认识到这是经典的多方法要求,即一种基于多个参数的运行时类型进行调度的方法。相比之下,通常的虚函数是 single dispatch(它们仅在 this 类型上调度)。

        请参阅以下内容:

        • Andrei Alexandrescu 撰写了(C++ 的开创性位?)关于在“现代 C++ 设计”中使用泛型实现多方法的文章
          • Chapter 11: "Multimethods" - 它实现了基本的多方法,使它们成为对数(使用有序类型列表),然后一直到恒定时间的多方法。相当强大的东西!
        • codeproject article 似乎有这样一个实现:
          • 不使用任何类型的类型转换(动态、静态、重新解释、const 或 C 风格)
          • 不使用 RTTI;
          • 不使用预处理器;
          • 强型安全;
          • 单独编译;
          • 多方法执行的恒定时间;
          • 在多方法调用期间没有动态内存分配(通过 new 或 malloc);
          • 不使用非标准库;
          • 仅使用标准 C++ 功能。
        • C++ Open Method Compiler、Peter Pirkelbauer、Yuriy Solodkyy 和 Bjarne Stroustrup
        • Lo​​ki 图书馆有A MultipleDispatcher
        • Wikipedia 有一个很好的 simple write-up,其中包含有关 C++ 中的 Multiple Dispatch 的示例。

        这是*文章中的“简单”方法供参考(不太简单的方法适用于大量派生类型):

        // Example using run time type comparison via dynamic_cast
        
        struct Thing {
            virtual void collideWith(Thing& other) = 0;
        }
        
        struct Asteroid : Thing {
            void collideWith(Thing& other) {
                // dynamic_cast to a pointer type returns NULL if the cast fails
                // (dynamic_cast to a reference type would throw an exception on failure)
                if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
                    // handle Asteroid-Asteroid collision
                } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
                    // handle Asteroid-Spaceship collision
                } else {
                    // default collision handling here
                }
            }
        }
        
        struct Spaceship : Thing {
            void collideWith(Thing& other) {
                if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
                    // handle Spaceship-Asteroid collision
                } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
                    // handle Spaceship-Spaceship collision
                } else {
                    // default collision handling here
                }
            }
        }
        

        【讨论】: