如何在C++中实现"虚拟模板功能"

Sha*_*dow 41 c++ virtual templates

首先:我已阅读并且我现在知道在C++中虚拟模板成员函数(但是?)是不可能的.解决方法是将类作为模板,然后在member-function中使用template-argument.

但是在OOP的上下文中,我发现如果该类实际上是一个模板,下面的例子将不会非常"自然".请注意,代码实际上不起作用,但gcc-4.3.4报告:error: templates may not be ‘virtual’

#include <iostream>
#include <vector>

class Animal {
    public:
        template< class AMOUNT >
        virtual void eat( AMOUNT amount ) const { 
            std::cout << "I eat like a generic Animal." << std::endl; 
        }
        virtual ~Animal() { 
        }
};

class Wolf : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a wolf!" << std::endl; 
        }
        virtual ~Wolf() { 
        }
};

class Fish : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a fish!" << std::endl; 
        }
        virtual ~Fish() { 
        }
};

class GoldFish : public Fish {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a goldfish!" << std::endl; 
        }
        virtual ~GoldFish() { 
        }
};

class OtherAnimal : public Animal {
        virtual ~OtherAnimal() { 
        }
};

int main() {
    std::vector<Animal*> animals;
    animals.push_back(new Animal());
    animals.push_back(new Wolf());
    animals.push_back(new Fish());
    animals.push_back(new GoldFish());
    animals.push_back(new OtherAnimal());

    for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->eat();
        delete *it;
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以创造一个"Fish <Amount> foo"有点奇怪.然而,我似乎希望为每只动物提供任意数量的食物.

因此,我正在寻找一个如何实现类似的解决方案

Fish bar;
bar.eat( SomeAmount food );
Run Code Online (Sandbox Code Playgroud)

在查看for循环时,这变得特别有用.有人可能想给所有不同的动物喂食一定量的食物(FoodAmount)(通过eat()和bind1st()例如),虽然我觉得这个非常直观(因此在一定程度上)虽然有些人可能想现在争辩说这是由于矢量的"统一"特征,但我认为/希望能够实现这一点并且我真的想知道如何,因为这是让我困惑了一段时间......

[编辑]

为了澄清我的问题背后的动机,我想编写一个Exporter-class,让不同的,更专业的类派生出来.虽然顶层Exporter-class通常仅用于化妆品/结构目的,但派生了一个GraphExporter类,它应该再次作为基础类进行更加特殊的导出.但是,类似于Animal-example,我希望能够在专门的/派生类(例如,在SpecialGraphExplorer上)定义GraphExporter*,但是在调用"write(out_file)"时,它应该调用SpecialGraphExporter的相应成员函数GraphExporter :: write(out_file).

也许这使我的情况和意图更加清晰.

最好,

阴影

seh*_*ehe 31

经过一番思考后,我认为这是经典的多方法要求,即根据多个参数的运行时类型进行调度的方法.通常的虚拟功能是single dispatch比较的(并且它们this仅在类型上发送).

请参阅以下内容:

  • Andrei Alexandrescu在"现代C++设计"中使用泛型实现了多方法的写作(C++的开创性内容?)
    • 第11章:"多方法" - 它实现了基本的多方法,使它们成为对数(使用有序的类型列表),然后一直到恒定时间的多方法.相当强大的东西!
  • 一个似乎只有这样一个实现的代码项目文章:
    • 不使用任何类型的类型转换(动态,静态,重新解释,const或C风格)
    • 不使用RTTI;
    • 不使用预处理器;
    • 强大的安全性;
    • 单独编制;
    • 多方法执行的恒定时间;
    • 在multimethod调用期间没有动态内存分配(通过new或malloc);
    • 不使用非标准图书馆;
    • 仅使用标准C++功能.
  • C++ Open Method Compiler,Peter Pirkelbauer,Yuriy Solodkyy和Bjarne Stroustrup
  • Loki图书馆有一个MultipleDispatcher
  • 维基百科有一个非常简单的简单文章,其中包含C++中的Multi 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mik*_*son 13

显然,虚拟成员函数模板是不允许的,甚至在理论上也无法实现.要构建基类的虚拟表,需要有一定数量的虚函数指针条目.函数模板将允许无限量的"重载"(即实例化).

从理论上讲,语言(如C++)可以允许虚拟成员函数模板,如果它有一些机制来指定实例(有限)实例化列表.C++确实有这种机制(即显式模板实例化),所以我想有可能在更新的C++标准中做到这一点(虽然我不知道编译器厂商实现这个功能会带来什么麻烦).但是,这只是一个理论上的讨论,在实践中,根本不允许这样做.事实仍然是,你必须使虚拟函数的数量有限(不允许模板).

当然,这并不意味着类模板不能具有虚函数,也不意味着虚函数不能调用函数模板.因此,有许多解决方案(如访客模式或其他方案).

我认为,一个优雅的解决方案(尽管很难理解)是以下(基本上是访客模式):

#include <iostream>
#include <vector>

struct Eater { 
  virtual void operator()(int amount) const = 0;
  virtual void operator()(double amount) const = 0;
};

template <typename EaterType>
struct Eater_impl : Eater {
  EaterType& data;
  Eater_impl(EaterType& aData) : data(aData) { };
  virtual void operator()(int amount) const { data.eat_impl(amount); };
  virtual void operator()(double amount) const { data.eat_impl(amount); };
};

class Animal {
  protected:
    Animal(Eater& aEat) : eat(aEat) { };
  public:
    Eater& eat;
    virtual ~Animal() { delete &eat; };
};

class Wolf : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a wolf!" << std::endl; 
    }

  public:
    friend struct Eater_impl<Wolf>;        
    Wolf() : Animal(*(new Eater_impl<Wolf>(*this))) { };
    virtual ~Wolf() { };
};

class Fish : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a fish!" << std::endl; 
    }
  public:
    friend struct Eater_impl<Fish>;
    Fish() : Animal(*(new Eater_impl<Fish>(*this))) { };
    virtual ~Fish() { };
};

int main() {
  std::vector<Animal*> animals;
  animals.push_back(new Wolf());
  animals.push_back(new Fish());

  for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
    (*it)->eat(int(0));
    (*it)->eat(double(0.0));
    delete *it;
  };

  return 0;
};
Run Code Online (Sandbox Code Playgroud)

以上是一个简洁的解决方案,因为它允许您在一个地方定义您想要的有限数量的重载(在Eater_impl类模板中),并且派生类中您需要的只是一个函数模板(可能还有额外的重载,特别案例).当然,还有一些开销,但我想可以多考虑​​一下来减少开销(额外的参考存储和Eater_impl的动态分配).我猜,奇怪的重复模板模式可能会以某种方式用于此目的.


Ind*_*ant 11

我认为访客模式可以是一个解决方案.

UPDATE

我完成了我的例子:

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

class Animal;
class Wolf;
class Fish;

class Visitor
{
    public:
    virtual void visit(const Animal& p_animal) const = 0;
    virtual void visit(const Wolf& p_animal) const = 0;
    virtual void visit(const Fish& p_animal) const = 0;
};

template<class AMOUNT>
class AmountVisitor : public Visitor
{
    public:
    AmountVisitor(AMOUNT p_amount) : m_amount(p_amount) {}
    virtual void visit(const Animal& p_animal) const
    {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual void visit(const Wolf& p_animal) const
    {
        std::cout << "I eat like a wolf!" << std::endl;
    }
    virtual void visit(const Fish& p_animal) const
    {
        std::cout << "I eat like a fish!" << std::endl;
    }


    AMOUNT m_amount;
};

class Animal {
    public:

        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }

        virtual ~Animal() {
        }
};

class Wolf : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

class Fish : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

int main()
{
    typedef boost::shared_ptr<Animal> TAnimal;
    std::vector<TAnimal> animals;
    animals.push_back(TAnimal(new Animal()));
    animals.push_back(TAnimal(new Wolf()));
    animals.push_back(TAnimal(new Fish()));

    AmountVisitor<int> amount(10);

    for (std::vector<TAnimal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->Accept(amount);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这打印:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
Run Code Online (Sandbox Code Playgroud)

  • 有可能的。例如,您可以在访问者类中使用责任链模式。只需谷歌“c++ 责任链模式”即可。 (2认同)

eac*_*eau 5

根据 Mikael 的帖子,我做了另一个分支,使用 CRTP 并遵循 Eigenderived()用于显式子类引用的风格:

// Adaptation of Visitor Pattern / CRTP from:
// http://stackoverflow.com/a/5872633/170413

#include <iostream>
using std::cout;
using std::endl;

class Base {
public:
  virtual void tpl(int x) = 0;
  virtual void tpl(double x) = 0;
};

// Generics for display
template<typename T>
struct trait {
  static inline const char* name() { return "T"; }
};
template<>
struct trait<int> {
  static inline const char* name() { return "int"; }
};
template<>
struct trait<double> {
  static inline const char* name() { return "double"; }
};

// Use CRTP for dispatch
// Also specify base type to allow for multiple generations
template<typename BaseType, typename DerivedType>
class BaseImpl : public BaseType {
public:
  void tpl(int x) override {
    derived()->tpl_impl(x);
  }
  void tpl(double x) override {
    derived()->tpl_impl(x);
  }
private:
  // Eigen-style
  inline DerivedType* derived() {
    return static_cast<DerivedType*>(this);
  }
  inline const DerivedType* derived() const {
    return static_cast<const DerivedType*>(this);
  }
};

// Have Child extend indirectly from Base
class Child : public BaseImpl<Base, Child> {
protected:
  friend class BaseImpl<Base, Child>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "Child::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};

// Have SubChild extend indirectly from Child
class SubChild : public BaseImpl<Child, SubChild> {
protected:
  friend class BaseImpl<Child, SubChild>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "SubChild::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};


template<typename BaseType>
void example(BaseType *p) {
  p->tpl(2);
  p->tpl(3.0);
}

int main() {
  Child c;
  SubChild sc;

  // Polymorphism works for Base as base type
  example<Base>(&c);
  example<Base>(&sc);
  // Polymorphism works for Child as base type
  example<Child>(&sc);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Child::tpl_impl<int>(2)
Child::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)
Run Code Online (Sandbox Code Playgroud)

这个片段可以在这里的源代码中找到:repro:c808ef0:cpp_quick/virtual_template.cc


AJG*_*G85 2

不允许使用虚拟模板功能。不过,您可以在此处使用其中之一或另一个。

您可以使用虚拟方法创建一个界面,并通过饮食界面来实现各种动物。(即 PIMPL)

不太人类的直觉是将非成员非朋友模板函数作为自由函数,它可以对任何动物进行模板化 const 引用并让它们相应地吃东西。

根据记录,您不需要此处的模板。基类上的纯虚拟抽象方法足以强制和连接所有动物必须吃的地方,并定义它们如何通过覆盖来做到这一点,提供常规虚拟就足以说明所有动物都可以吃,但如果它们没有特定方式,然后他们可以使用此默认方式。