如何实现“虚拟模板功能”在 C++

发布于 2024-11-04 16:08:25 字数 2534 浏览 1 评论 0原文

首先:我已经阅读过并且现在知道虚拟模板成员函数在 C++ 中(还?) 不可能。解决方法是使类成为模板,然后在成员函数中也使用模板参数。

但在 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;
}

因此创建“Fishfoo”有点奇怪。然而,我似乎希望为每只动物提供任意数量的食物。

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

Fish bar;
bar.eat( SomeAmount food );

这在查看 for 循环时变得特别有用。人们可能想给所有不同的动物喂食特定量(FoodAmount)(通过 eat() 和 bind1st() 例如),这不可能那么容易做到,尽管我觉得这非常直观(因此在某种程度上) “自然”)。虽然现在有些人可能想争辩说这是由于向量的“统一”特征造成的,但我认为/希望应该可以实现这一目标,而且我真的很想知道如何实现,因为这是让我很困惑现在一段时间...

[编辑]

为了澄清我的问题背后的动机,我想编写一个 Exporter 类并让不同的、更专业的类从它派生,而顶级 Exporter 则从中派生。 -class 通常仅用于装饰/结构目的,派生出 GraphExporter 类,该类应再次作为更专业导出的基类但是,与 Animal 示例类似,我希望能够定义。 GraphExporter* 甚至在专门/派生上类(例如在 SpecialGraphExplorer 上),但是当调用“write( out_file )”时,它应该调用 SpecialGraphExporter 的适当成员函数而不是 GraphExporter::write( out_file) 。

也许这让我的处境和意图更加清晰。

最好的,

影子

first off: I have read and I know now that a virtual template member function is not (yet?) possible in C++. A workaround would be to make the class a template and then use the template-argument also in the member-function.

But in the context of OOP, I find that the below example would not be very "natural" if the class was actually a template. Please note that the code is actually not working, but the gcc-4.3.4 reports: 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;
}

So creating a "Fish< Amount > foo" is kind of strange. However, it seems desirable to me to provide an arbitrary amount of food to eat for each animal.

Thus, I am searching a solution about how to achieve something like

Fish bar;
bar.eat( SomeAmount food );

This becomes particularly useful when looking at the for-loop. One might like to feed a specific amount (FoodAmount) to all of the different animals (via eat() and bind1st() e.g.), it could not be done that easily, although I wound find this very inuitive (and thus to some extent "natural). While some might want to argue now that this is due to the "uniform"-character of a vector, I think/wish that it should be possible to achieve this and I really would like to know how, as this is puzzling me for quite some time now...

[EDIT]

To perhaps clarify the motivation behind my question, I want to program an Exporter-class and let different, more specialized classes derive from it. While the top-level Exporter-class is generally only for cosmetic/structural purpose, a GraphExporter-class is derived, that should again serve as a base-class for even more specialzed export. However, similar to the Animal-example, I would like to be able to define GraphExporter* even on specialized/derived classes (e.g. on SpecialGraphExplorer) but when calling "write( out_file )", it should call the appropriate member function for SpecialGraphExporter instead of GraphExporter::write( out_file).

Maybe this makes my situation and intentions clearer.

Best,

Shadow

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

久随 2024-11-11 16:08:26

我不使用模板,但我认为:

(1)你不能在类中使用模板,模板更像是全局类型或全局变量。

(2) 在 OOP 中,您提出的相同问题以及您尝试使用模板解决的问题,可以通过使用继承来解决。

类的工作方式与模板类似,您可以通过添加新内容来扩展,或者用指针、指向对象的指针(也称为“引用”)和重写虚函数来替换类的内容。

#include <iostream>

struct Animal {
    virtual void eat(int amount ) {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual ~Animal() { }
};

#if 0

// example 1
struct Wolf : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a wolf!" << std::endl;
    }
};

struct Fish : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a fish!" << std::endl;
    }
};

#else

// example 2

struct AnimalFood {
    virtual int readAmount() { return 5; }

    virtual void showName() {
        std::cout << "I'm generic animal food" << std::endl;
    }
};

struct PredatorFood : AnimalFood {
    virtual int readAmount() { return 500; }

    virtual void showName() {
        std::cout << "I'm food for a predator" << std::endl;
    }
};


struct Fish : Animal {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 50) {
            std::cout << "OK food, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "too much food, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Shark : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 250) {
            std::cout << "too litle food for a shark, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Wolf : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 150) {
            std::cout << "too litle food for a wolf, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

#endif

int main() {
    // find animals
    Wolf* loneWolf = new Wolf();
    Fish* goldenFish = new Fish();
    Shark* sharky = new Shark();

    // prepare food
    AnimalFood* genericFood = new AnimalFood();
    PredatorFood* bigAnimalFood = new PredatorFood();

    // give food to animals
    loneWolf->eat(genericFood);
    loneWolf->eat(bigAnimalFood);

    goldenFish->eat(genericFood);
    goldenFish->eat(bigAnimalFood);

    sharky->eat(genericFood);
    sharky->eat(bigAnimalFood);

    delete bigAnimalFood;
    delete genericFood;

    delete sharky;
    delete goldenFish;
    delete loneWolf;
}

干杯。

I don't work with templates, but I think:

(1) You cannot use templates inside a class, templates are more like global types or global variables.

(2) In O.O.P., the same problem you present, and that you are trying to solve by using templates, can be solved by using inheritance.

Classes work similar to templates, you can extended by adding new things, or replace things of classes with pointers, pointers to objects (A.K.A. "references") and overriding virtual functions.

#include <iostream>

struct Animal {
    virtual void eat(int amount ) {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual ~Animal() { }
};

#if 0

// example 1
struct Wolf : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a wolf!" << std::endl;
    }
};

struct Fish : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a fish!" << std::endl;
    }
};

#else

// example 2

struct AnimalFood {
    virtual int readAmount() { return 5; }

    virtual void showName() {
        std::cout << "I'm generic animal food" << std::endl;
    }
};

struct PredatorFood : AnimalFood {
    virtual int readAmount() { return 500; }

    virtual void showName() {
        std::cout << "I'm food for a predator" << std::endl;
    }
};


struct Fish : Animal {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 50) {
            std::cout << "OK food, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "too much food, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Shark : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 250) {
            std::cout << "too litle food for a shark, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Wolf : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 150) {
            std::cout << "too litle food for a wolf, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

#endif

int main() {
    // find animals
    Wolf* loneWolf = new Wolf();
    Fish* goldenFish = new Fish();
    Shark* sharky = new Shark();

    // prepare food
    AnimalFood* genericFood = new AnimalFood();
    PredatorFood* bigAnimalFood = new PredatorFood();

    // give food to animals
    loneWolf->eat(genericFood);
    loneWolf->eat(bigAnimalFood);

    goldenFish->eat(genericFood);
    goldenFish->eat(bigAnimalFood);

    sharky->eat(genericFood);
    sharky->eat(bigAnimalFood);

    delete bigAnimalFood;
    delete genericFood;

    delete sharky;
    delete goldenFish;
    delete loneWolf;
}

Cheers.

南城旧梦 2024-11-11 16:08:25

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

请参阅以下内容:

  • Andrei Alexandrescu 撰写了关于在“现代 C++ 设计”中使用泛型实现多种方法的文章(C++ 的开创性位?)
    • 第 11 章:“多方法” - 它实现了基本的多方法,使它们对数(使用有序类型列表),然后一直到恒定时间多方法。相当强大的东西!
  • 一篇 codeproject 文章 似乎就有这样的实现:
    • 不使用任何类型的类型转换(动态、静态、重新解释、const 或 C 风格)
    • 不使用 RTTI;
    • 不使用预处理器;
    • 强类型安全性;
    • 单独编译;
    • 多方法执行时间恒定;
    • 在多方法调用期间没有动态内存分配(通过 new 或 malloc);
    • 不使用非标准库;
    • 仅使用标准 C++ 功能。
  • 器,Peter Pirkelbauer、Yuriy Solodkyy 和 Bjarne Stroustrup
  • C++ 开放方法编译 Loki 库有 A MultipleDispatcher
  • 维基百科有一个相当不错的简单的编写-up 包含有关 C++ 中的多重调度的示例。

以下是维基百科文章中的“简单”方法供参考(不太简单的方法对于大量派生类型的扩展效果更好):

// 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
        }
    }
}

After some thinking I recognized this as the classic multi-method requirement, i.e. a method that dispatches based on the runtime type of more than one parameter. Usual virtual functions are single dispatch in comparison (and they dispatch on the type of this only).

Refer to the following:

  • Andrei Alexandrescu has written (the seminal bits for C++?) on implementing multi-methods using generics in 'Modern C++ design'
    • Chapter 11: "Multimethods" - it implements basic multi-methods, making them logarithmic (using ordered typelists) and then going all the way to constant-time multi-methods. Quite powerful stuff !
  • A codeproject article that seems to have just such an implementation:
    • no use of type casts of any kind (dynamic, static, reinterpret, const or C-style)
    • no use of RTTI;
    • no use of preprocessor;
    • strong type safety;
    • separate compilation;
    • constant time of multimethod execution;
    • no dynamic memory allocation (via new or malloc) during multimethod call;
    • no use of nonstandard libraries;
    • only standard C++ features is used.
  • C++ Open Method Compiler, Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne Stroustrup
  • The Loki Library has A MultipleDispatcher
  • Wikipedia has quite a nice simple write-up with examples on Multiple Dispatch in C++.

Here is the 'simple' approach from the wikipedia article for reference (the less simple approach scales better for larger number of derived types):

// 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
        }
    }
}

一抹苦笑 2024-11-11 16:08:25

显然,虚拟成员函数模板是不允许的,理论上也是无法实现的。为了构建基类的虚拟表,需要有有限数量的虚拟函数指针条目。函数模板将允许无限量的“重载”(即实例化)。

从理论上讲,如果语言(如 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;
};

上面是一个简洁的解决方案,因为它允许您定义有限数量的重载只需在一处(在 Eater_impl 类模板中),而派生类中您所需要的只是一个函数模板(对于特殊情况,可能还需要额外的重载)。当然,有一点开销,但我想可以多花点心思来减少开销(额外的引用存储和 Eater_impl 的动态分配)。我猜想,奇怪的重复模板模式可能会以某种方式用于此目的。

Obviously, virtual member function templates are not allowed and could not be realized even theoretically. To build a base class' virtual table, there needs to be a finite number of virtual function-pointer entries. A function template would admit an indefinite amount of "overloads" (i.e. instantiations).

Theoretically-speaking, a language (like C++) could allow virtual member function templates if it had some mechanism to specify the actual (finite) list of instantiations. C++ does have that mechanism (i.e. explicit template instantiations), so I guess it could be possible to do this in a newer C++ standard (although I have no idea what trouble it would entail for compiler vendors to implement this feature). But, that's just a theoretical discussion, in practice, this is simply not allowed. The fact remains, you have to make the number of virtual functions finite (no templates allowed).

Of course, that doesn't mean that class template cannot have virtual functions, nor does it mean that virtual functions cannot call function templates. So, there are many solutions in that vein (like the Visitor pattern or other schemes).

One solution that, I think, serves your purpose (although it is hard to comprehend) elegantly is the following (which is basically a visitor pattern):

#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;
};

The above is a neat solution because it allows you to define a finite number of overloads that you want in one place only (in the Eater_impl class template) and all you need in the derived class is a function template (and possibly additional overloads, for special cases). There is, of course, a bit of overhead, but I guess that a bit more thought could be put into it to reduce the overhead (additional reference storage and dynamic allocation of Eater_impl). I guess, the curiously recurring template pattern could probably be employed somehow to this end.

几度春秋 2024-11-11 16:08:25

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

更新

我完成了我的示例:

#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;
}

打印:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!

I think the visitor pattern can be a solution.

UPDATE

I finished my example:

#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;
}

this prints:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
御守 2024-11-11 16:08:25

根据 Mikael 的帖子,我制作了另一个分支,使用 CRTP 并遵循 Eigen 的使用 categories() 进行显式子类引用的风格:

// 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;
}

输出:

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)

此代码段可以在此处的源代码中找到:重现:c808ef0:cpp_quick/virtual_template.cc

Per Mikael's post, I have made another offshoot, using the CRTP and following Eigen's style of using derived() for an explicit subclass reference:

// 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;
}

Output:

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)

This snippet may be found in source here: repro:c808ef0:cpp_quick/virtual_template.cc

温柔一刀 2024-11-11 16:08:25

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

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

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

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

Virtual template function is not allowed. However you can use one OR the other here.

You could make an interface using virtual methods and implement your various animals in terms of having an eating interface. (i.e. PIMPL)

Less human intuitive would be having a non-member non-friend template function as a free function which could take templated const reference to any animal and make them eat accordingly.

For the record you don't need templates here. Pure virtual abstract method on the base class is enough to force and interface where all animals must eat and define how they do so with an override, providing a regular virtual would be enough to say all animals can eat but if they don't have a specific way then they can use this default way.

掌心的温暖 2024-11-11 16:08:25

您可以使用虚函数创建一个模板类,并在派生类中实现该函数,而不使用模板,如下所示:

a.h:

template <class T>
class A
{
public:
    A() { qDebug() << "a"; }

    virtual A* Func(T _template) { return new A;}
};


b.h:

class B : public A<int>
{
public:
    B();
    virtual A* Func(int _template) { return new B;}
};


and the function CTOR and call 

  A<int>* a1=new B;
    int x=1;
    a1->Func(x);

不幸的是,我还没有找到一种方法来创建带有模板参数的虚函数,而不将该类声明为模板,并且它是派生类上的模板类型

You can create a template class with virtual function, and implement the function in the derived class without using template in the follwing way:

a.h:

template <class T>
class A
{
public:
    A() { qDebug() << "a"; }

    virtual A* Func(T _template) { return new A;}
};


b.h:

class B : public A<int>
{
public:
    B();
    virtual A* Func(int _template) { return new B;}
};


and the function CTOR and call 

  A<int>* a1=new B;
    int x=1;
    a1->Func(x);

unfortunately i havn't found a way to create a virtual function with template parameters without declaring the class as a template and it template type on the dervied class

望她远 2024-11-11 16:08:25

我已经复制了您的代码并对其进行了修改,所以现在它应该完全按照您的要求工作:

        #include <iostream>
        #include <vector>

        //defined new enum type
        enum AnimalEnum
        {
           animal,
           wolf,
           fish,
           goldfish,
           other
        };

        //forward declarations
        class Wolf;
        class Fish;
        class GoldFish;
        class OtherAnimal;

        class Animal {
            private:
            AnimalEnum who_really_am_I;
            void* animal_ptr;
            public:
                //declared new constructors overloads for each type of animal
                Animal(const Animal&);
                Animal(const Wolf&);
                Animal(const Fish&);
                Animal(const GoldFish&);
                Animal(const OtherAnimal&);
                template< class AMOUNT >
                /*removed the virtual keyword*/ void eat( AMOUNT amount ) const { 
                    switch (this->who_really_am_I)
                    {
                       case AnimalEnum::other: //You defined OtherAnimal so that it doesn't override the eat action, so it will uses it's Animal's eat
                       case AnimalEnum::animal: std::cout << "I eat like a generic Animal." << std::endl; break;
                       case AnimalEnum::wolf: ((Wolf*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::fish: ((Fish*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::goldfish: ((GoldFish*)this->animal_ptr)->eat(amount) break;
                    }
                }
                void DeleteMemory() { delete this->animal_ptr; }
                virtual ~Animal() { 
                   //there you can choose if whether or not to delete "animal_ptr" here if you want or not
                }
        };

        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 {
                //OtherAnimal constructors must be defined here as Animal's constructors
                OtherAnimal(const Animal& a) : Animal(a) {}
                OtherAnimal(const Wolf& w) : Animal(w) {}
                OtherAnimal(const Fish& f) : Animal(f) {}
                OtherAnimal(const GoldFish& g) : Animal(g) {}
                OtherAnimal(const OtherAnimal& o) : Animal(o) {}
                virtual ~OtherAnimal() { 
                }
        };
        //OtherAnimal will be useful only if it has it's own actions and members, because if not, typedef Animal OtherAnimal or using OtherAnimal = Animal can be used, and it can be removed from above declarations and below definitions

//Here are the definitions of Animal constructors that were declared above/before:    
        Animal::Animal(const Animal& a) : who_really_am_I(AnimalEnum::animal), animal_ptr(nullptr) {}

        Animal::Animal(const Wolf& w) : who_really_am_I(AnimalEnum::wolf), animal_ptr(new Wolf(w)) {}

        Animal::Animal(const Fish& f) : who_really_am_I(AnimalEnum::fish), animal_ptr(new Fish(f)) {}

        Animal::Animal(const GoldFish& g) : who_really_am_I(AnimalEnum::goldfish), animal_ptr(new GoldFish(g)) {}

        Animal::Animal(const OtherAnimal& o) :
    who_really_am_I(AnimalEnum::other), animal_ptr(new OtherAnimal(o)) {}

        int main() {
            std::vector<Animal> animals;
            animals.push_back(Animal());
            animals.push_back(Wolf()); //Wolf is converted to Animal via constructor
            animals.push_back(Fish()); //Fish is converted to Animal via constructor
            animals.push_back(GoldFish()); //GoldFish is converted to Animal via constructor
            animals.push_back(OtherAnimal()); //OtherAnimal is converted to Animal via constructor

            for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
                it->eat(); //this is Animal's eat that invokes other animals eat
                //delete *it; Now it should be:
                it->DeleteMemory();
            }
            animals.clear(); //All animals have been killed, and we don't want full vector of dead animals.

            return 0;
        }

I have copied your code and modified it, so now it should work exactly as you want:

        #include <iostream>
        #include <vector>

        //defined new enum type
        enum AnimalEnum
        {
           animal,
           wolf,
           fish,
           goldfish,
           other
        };

        //forward declarations
        class Wolf;
        class Fish;
        class GoldFish;
        class OtherAnimal;

        class Animal {
            private:
            AnimalEnum who_really_am_I;
            void* animal_ptr;
            public:
                //declared new constructors overloads for each type of animal
                Animal(const Animal&);
                Animal(const Wolf&);
                Animal(const Fish&);
                Animal(const GoldFish&);
                Animal(const OtherAnimal&);
                template< class AMOUNT >
                /*removed the virtual keyword*/ void eat( AMOUNT amount ) const { 
                    switch (this->who_really_am_I)
                    {
                       case AnimalEnum::other: //You defined OtherAnimal so that it doesn't override the eat action, so it will uses it's Animal's eat
                       case AnimalEnum::animal: std::cout << "I eat like a generic Animal." << std::endl; break;
                       case AnimalEnum::wolf: ((Wolf*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::fish: ((Fish*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::goldfish: ((GoldFish*)this->animal_ptr)->eat(amount) break;
                    }
                }
                void DeleteMemory() { delete this->animal_ptr; }
                virtual ~Animal() { 
                   //there you can choose if whether or not to delete "animal_ptr" here if you want or not
                }
        };

        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 {
                //OtherAnimal constructors must be defined here as Animal's constructors
                OtherAnimal(const Animal& a) : Animal(a) {}
                OtherAnimal(const Wolf& w) : Animal(w) {}
                OtherAnimal(const Fish& f) : Animal(f) {}
                OtherAnimal(const GoldFish& g) : Animal(g) {}
                OtherAnimal(const OtherAnimal& o) : Animal(o) {}
                virtual ~OtherAnimal() { 
                }
        };
        //OtherAnimal will be useful only if it has it's own actions and members, because if not, typedef Animal OtherAnimal or using OtherAnimal = Animal can be used, and it can be removed from above declarations and below definitions

//Here are the definitions of Animal constructors that were declared above/before:    
        Animal::Animal(const Animal& a) : who_really_am_I(AnimalEnum::animal), animal_ptr(nullptr) {}

        Animal::Animal(const Wolf& w) : who_really_am_I(AnimalEnum::wolf), animal_ptr(new Wolf(w)) {}

        Animal::Animal(const Fish& f) : who_really_am_I(AnimalEnum::fish), animal_ptr(new Fish(f)) {}

        Animal::Animal(const GoldFish& g) : who_really_am_I(AnimalEnum::goldfish), animal_ptr(new GoldFish(g)) {}

        Animal::Animal(const OtherAnimal& o) :
    who_really_am_I(AnimalEnum::other), animal_ptr(new OtherAnimal(o)) {}

        int main() {
            std::vector<Animal> animals;
            animals.push_back(Animal());
            animals.push_back(Wolf()); //Wolf is converted to Animal via constructor
            animals.push_back(Fish()); //Fish is converted to Animal via constructor
            animals.push_back(GoldFish()); //GoldFish is converted to Animal via constructor
            animals.push_back(OtherAnimal()); //OtherAnimal is converted to Animal via constructor

            for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
                it->eat(); //this is Animal's eat that invokes other animals eat
                //delete *it; Now it should be:
                it->DeleteMemory();
            }
            animals.clear(); //All animals have been killed, and we don't want full vector of dead animals.

            return 0;
        }
前事休说 2024-11-11 16:08:25

在您的场景中,您尝试将编译时多态性与运行时多态性混合在一起,但不能在这个“方向”上完成。

重要的是,您的 AMOUNT 模板参数表示要基于每个 eat 实现使用的所有操作的并集来实现的类型的预期接口。如果您在哪里创建一个抽象类型来声明每个操作,使它们在需要时成为虚拟的,那么您可以使用不同的类型(派生自 AMOUNT 接口)来调用 eat 。它会按预期运行。

In you scenario, you are trying to mix compile time polymorphism with runtime polymorphism, but it cannot be done in this "direction".

Essential, your AMOUNT template argument represents an expected interface for the type to implement based on the union of all the operations each implementation of eat uses. If you where to create an abstract type that declared each of those operations making them virtual where needed, then you could call eat with different types (that derived from your AMOUNT interface). And it would behave as expected.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文