【问题标题】:How to create a specialized and default versions of a function that take base and derived classes?如何创建采用基类和派生类的函数的专用和默认版本?
【发布时间】:2025-12-01 04:50:02
【问题描述】:

我有以下类架构:

class Animal 
{
 // ...
}

class Cat : public Animal
{
 // ...
}

class Dog : public Animal
{
 // ...
}

// + Several other derived classes

在我的代码的另一部分,我有一个函数,它遍历一个动物列表,并且需要在几个派生类的情况下执行专门的操作,否则需要执行默认操作。考虑到以下限制,我该如何优雅地处理这种情况:

  1. 我想将新代码保留在 Animal 及其衍生代码之外 因为关注点分离而分类。
  2. 我想避免在类型或枚举上使用 switch 语句,因为它感觉很臭。

【问题讨论】:

  • 您可以阅读double dispatch in C++ 以获得一些想法。
  • 您能详细说明“动物列表”吗?你的意思是list<Animal*>

标签: c++ templates inheritance


【解决方案1】:

这是一种方法 - 使用概念模型成语(我的名字):

#include <iostream>
#include <vector>

struct AnimalConcept {
    virtual ~AnimalConcept() = default;

    virtual void make_noise() const = 0;
};

// default case
void make_noise_for(const AnimalConcept&)
{
    std::cout << "no noise" << std::endl;
}

template<class Model>
struct AnimalModel : AnimalConcept
{

    void make_noise() const override {
        make_noise_for(static_cast<const Model&>(*this));
    }
};

// some models

struct Cat : AnimalModel<Cat>
{

};

struct Dog : AnimalModel<Dog>
{

};

struct Giraffe : AnimalModel<Giraffe>
{

};

// separation of concerns - specific overrides

void make_noise_for(const Cat&) {
    std::cout << "meow\n";
}

void make_noise_for(const Dog&) {
    std::cout << "woof\n";
}

// test

using namespace std;

int main(){
    std::vector<std::unique_ptr<const AnimalConcept>> animals;
    animals.emplace_back(new Cat);
    animals.emplace_back(new Dog);
    animals.emplace_back(new Giraffe);

    for (const auto& p : animals) {
        p->make_noise();
    }

    return 0;
}

预期输出:

meow
woof
no noise

这是另一种实现方式(这个更好,因为它允许所有动物拥有不相关的接口):

#include <iostream>
#include <vector>

struct AnimalConcept {
    virtual ~AnimalConcept() = default;

    virtual void make_noise() const = 0;
};

// default case
template<class T>
void make_noise_for(const T&)
{
    std::cout << "this animal makes no noise" << std::endl;
}

template<class Model>
struct AnimalModel : AnimalConcept
{
    template<class...Args>
    AnimalModel(Args&&...args)
    : _model { std::forward<Args>(args)... }
    {}

private:
    void make_noise() const override {
        make_noise_for(_model);
    }

    Model _model;
};

// some models

struct Cat
{
    Cat(std::string name)
    : _name { std::move(name) }
    {}

    const std::string& name() const {
        return _name;
    }

private:
    std::string _name;
};

struct Dog
{
    Dog(std::string name, int age)
    : _name { std::move(name) }
    , _age { age }
    {}

    const std::string& name() const {
        return _name;
    }

    int age() const {
        return _age;
    }

private:
    std::string _name;
    int _age;
};

struct Giraffe
{

};

// separation of concerns - specific overrides

void make_noise_for(const Cat& c) {
    std::cout << c.name() << " says meow\n";
}

void make_noise_for(const Dog& d) {
    std::cout << "the dog called " << d.name() << " who is " << d.age() << " years old says woof\n";
}

// test

using namespace std;

int main(){
    std::vector<std::unique_ptr<const AnimalConcept>> animals;
    animals.emplace_back(new AnimalModel<Cat> { "felix" });
    animals.emplace_back(new AnimalModel<Dog> { "fido", 2 });
    animals.emplace_back(new AnimalModel<Giraffe>);

    for (const auto& p : animals) {
        p->make_noise();
    }

    return 0;
}

预期输出:

felix says meow
the dog called fido who is 2 years old says woof
this animal makes no noise

【讨论】:

    【解决方案2】:

    您可以使用以下组合来获得基于类型的调度。

    1. 为每个类提供与其关联的类型 ID。
    2. 在基类中提供虚函数来获取与对象关联的类型 ID。
    3. 提供一种基于类型 ID 注册函数的方法。
    4. 当需要执行*函数时,搜索给定动物类型 ID 的已注册函数。如果注册了一个函数,请调用它。否则,使用默认函数。
    // Implement this function in a .cpp file.
    int getNextTypeID()
    {
       static int typeID = 0;
       return ++typeID;
    }
    
    class Animal 
    {
       virtual int getTypeID();
    };
    
    class Cat : public Animal
    {
       static int getID()
       {
          static int typeID = getNextTypeID();
       }
    
       virtual int getTypeID()
       {
          return getID();
       }
    };
    
    class Dog : public Animal
    {
       static int getID()
       {
          static int typeID = getNextTypeID();
       }
    
       virtual int getTypeID()
       {
          return getID();
       }
    };
    

    foo.h:

    typedef void (*AnimalFunction)(Animal& a);
    
    int registerAnimalFunctor(int typeID, AnimalFunction f);
    
    void foo(Animal& a);
    

    foo.cpp:

    typedef std::map<int, AnimalFunction> AnimalFunctionMap;
    
    AnimalFunctionMap& getAnimalFunctionMap()
    {
       static AnimalFunctionMap theMap;
       return theMap;
    }
    
    int registerAnimalFunctor(int typeID, AnimalFunction f)
    {
       getAnimalFunctionMap()[typeID] = f;
       return 0;
    }
    
    void defaultAnimalFunction(a)
    {
       // Default action
    }
    
    void foo(Animal& a)
    {
       AnimalFunctionMap& theMap = getAnimalFunctionMap();
       AnimalFunctionMap::iterator iter = theMap.find(a.getTypeID());
       if ( iter != theMap.end() )
       {
          iter->second(a);
       }
       else
       {
          defaultAnimalFunction(a);
       }
    }
    

    cat_foo.cpp:

    void CatFunction(Animal& a)
    {
       // Cat action.
    }
    
    int dummy = registerAnimalFunctor(Cat::getID(), CatFunction);
    

    dog_foo.cpp:

    void DogFunction(Animal& a)
    {
       // Dog action.
    }
    
    int dummy = registerAnimalFunctor(Dog::getID(), DogFunction);
    

    【讨论】: