在 C++ 中避免重复的 sub-class 定义

Avoiding repetitive sub-class definitions in C++

我是 C++ classes 的新手,我有一个关于定义抽象 type/interface 的多个子classes 的问题,它们将具有相同的定义。

以下面的例子为例,它可能出现在一个包含 3 个子 classes 的头文件中:

class Animal {
private:
    int a;
    int b;
public:
    explicit Animal(int a) {}
    virtual Animal* getFriend() = 0;
    virtual bool walk() = 0;
    virtual bool talk() = 0;
    virtual bool someFunction() = 0;
    virtual bool someOtherFunction() = 0;
    // ... many more functions
}

class Zebra: public Animal {
    Animal* getFriend();
    bool walk();
    bool someFunction();
    bool someOtherFunction();
    // ... continues for many more functions
}

class Cow: public Animal {
    Animal* getFriend();
    bool walk();
    bool someFunction();
    bool someOtherFunction();
    // ... continues for many more functions
}

class Salmon: public Animal {
    Animal* getFriend();
    bool walk();
    bool someFunction();
    bool someOtherFunction();
    // ... continues for many more functions
}

// ... many more animals

像这样声明子classes 似乎是重复的并且可能容易出错。因为 class 定义除了 class 的名称外都是相同的,是否有更有效的方法来批量声明动物子 classes?

在我工作的上下文中,每只动物都会在单独的 .cpp 文件中有一个完全独立的实现。

如果我的处理方式完全错误,请告诉我。任何帮助将不胜感激。

当您不为每个重写的虚拟 class 成员函数使用 override 关键字时确实容易出错。

而不是像这样声明派生的 class 函数

bool someFunction();

你can/should这样声明

bool someFunction() override;

这样,如果声明与基础 class 签名不匹配,就会出现编译错误。没有它,您将拥有一个非常好的可编译程序,但有一个行为错误。

除此之外,你的策略很好,是处理抽象函数的方法。

如果不使用宏来定义 classes(这更糟!),您可能无能为力。偶尔这样的东西可能会起作用,但我敢打赌,在 某些 点,你会想要专门化其中一种动物,导致你再次放弃宏。出于这个原因,我会避免使用该特定技术。

#define DECLARE_ANIMAL(ANIMAL_TYPE) \
class ANIMAL_TYPE: public Animal { \
    Animal* getFriend() override; \
    bool walk() override; \
    bool someFunction() override; \
    bool someOtherFunction() override; \
};
DECLARE_ANIMAL(Zebra);
DECLARE_ANIMAL(Cow);
DECLARE_ANIMAL(Salmon);

一般来说,尝试将尽可能多的重复 class 方法和数据移动到基础 class 中,以尽量减少代码重复量。不过,这可能需要您对问题的思考方式稍作改变....

例如walk()。在 Cow/Zebra/Horse/Cat/Dog 的情况下,行走的动作几乎相同。唯一真正的差异可以通过数据(例如步行速度,使用多少条腿,步行的步态是多少,每步幅有多大?)来衡量。如果您可以以数据驱动的方式定义行为,则只需在 Derived class 构造函数中设置这些参数,并避免需要 定制的方法。以这种方式处理 class 设计还有一些其他好处,例如,您将有一个 'Dog' class,但它可以代表一条 4 条腿的狗,以及一条 3有腿狗,无需创建新的 class。

无论如何,这通常是我推荐的方法...

我正在写另一个答案作为替代解决方案。实际上,如果我面对相同的 'issue' 或 'problem',我不会声明为批量,我只会创建 zebra.hzebra.cpp,继承自 Animal 和declare/define 所有成员单独。换句话说,我宁愿不聪明,但如果你想成为下面的代码片段可能是另一种选择。

实际上,您只想从模板创建一个 class 声明。这就是 template 正在做的事情。可以用 MACROs 模仿相同的行为,但我更喜欢 template 而不是 MACRO 因为这是 Bjarne 所做的。

所以这是代码

animal.h

#ifndef ANIMAL_H
#define ANIMAL_H

class Animal {
private:
    int a;
    int b;
public:
    explicit Animal(int a) {}
    virtual ~Animal() = default; // You should this virtual destructor
                                 // for polymorphic types.
    virtual Animal* getFriend() = 0;
    virtual bool walk() = 0;
    virtual bool talk() = 0;
    virtual bool someFunction() = 0;
    virtual bool someOtherFunction() = 0;
};

enum class animal_types
{
    zebra ,
    cow ,
    salmon ,
    special_animal
};

template< animal_types >
struct ugly_bulk_animal_inheritor : Animal
{
    using Animal::Animal; // Use parent constructor as is
    Animal* getFriend() override;
    bool walk() override;
    bool talk() override;
    bool someFunction() override;
    bool someOtherFunction() override;
};


using Zebra = ugly_bulk_animal_inheritor< animal_types::zebra >;
using Cow = ugly_bulk_animal_inheritor< animal_types::cow >;
using Salmon = ugly_bulk_animal_inheritor< animal_types::salmon >;

// So on..

#include "zebra.h"
#include "salmon.h"
#include "cow.h"
#include "special_animal.h"

#endif // ANIMAL_H

cow.h

#ifndef COW_H
#define COW_H

#include "animal.h"

template<>
Animal* Cow::getFriend() {
    return nullptr;
}

template<>
bool Cow::walk() {
    return true;
}

template<>
bool Cow::talk() {
    return false;
}

template<>
bool Cow::someFunction() {
    return true;
}

template<>
bool Cow::someOtherFunction() {
    return true;
}

#endif // COW_H

salmon.h

#ifndef SALMON_H
#define SALMON_H

#include "animal.h"

template<>
Animal* Salmon::getFriend() {
    return nullptr;
}

template<>
bool Salmon::walk() {
    return true;
}

template<>
bool Salmon::talk() {
    return true;
}

template<>
bool Salmon::someFunction() {
    return true;
}

template<>
bool Salmon::someOtherFunction() {
    return true;
}

#endif // SALMON_H

zebra.h

#ifndef ZEBRA_H
#define ZEBRA_H

#include "animal.h"

template<>
Animal* Zebra::getFriend() {
    return nullptr;
}

template<>
bool Zebra::walk() {
    return true;
}

template<>
bool Zebra::talk() {
    return false;
}

template<>
bool Zebra::someFunction() {
    return true;
}

template<>
bool Zebra::someOtherFunction() {
    return true;
}

#endif // ZEBRA_H

special_animal.h

#ifndef SPECIAL_ANIMAL_H
#define SPECIAL_ANIMAL_H

#include "animal.h"
#include <iostream>

template<>
struct ugly_bulk_animal_inheritor<animal_types::special_animal> : Animal
{
    using Animal::Animal; // Use parent constructor as is
    Animal* getFriend() override { return nullptr; }
    bool walk() override { return true; }
    bool talk() override { return true; }
    bool someFunction() override { return true; }
    bool someOtherFunction() override { return true; }

    void specility_fn() {
        std::cout << "A speciality" << std::endl;
    }

private:

    int some_extra_member;
    // etc..

};

using special_animal = ugly_bulk_animal_inheritor<animal_types::special_animal>;

#endif // SPECIAL_ANIMAL_H

main.cpp

#include <iostream>
#include "animal.h"

int main(int argc, char *argv[])
{
    Animal* instance;
    Zebra z { 5 };
    Cow c  { 6 };
    Salmon t { 7 };

    instance = &z;
    std::cout << "Zebra can talk ? " << instance->talk() << std::endl;
    instance = &t;
    std::cout << "Salmon can talk ? " << instance->talk() << std::endl;

    special_animal s { 5 };
    s.specility_fn();

    return 0;
}