避免在C++中重复子类定义

Avoiding repetitive sub-class definitions in C++

本文关键字:子类 定义 C++      更新时间:2023-10-16

我是C++类的新手,我有一个关于定义抽象类型/接口的多个子类的问题,这些子类将具有相同的定义。

以以下示例为例,该示例可能出现在具有 3 个子类的头文件中:

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

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

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

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

当您不为每个被覆盖的虚拟类成员函数使用override关键字时,它确实容易出错。

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

bool someFunction();

你可以/应该这样声明它

bool someFunction() override;

这样,如果声明与基类签名不匹配,则会收到编译错误。没有它,你将拥有一个非常好的可编译程序,但有一个行为错误。

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

我正在写另一个答案作为替代解决方案。实际上,如果我遇到相同的"问题"或"问题",我不会声明为批量,我只会创建zebra.hzebra.cpp,从Animal继承并单独声明/定义所有成员。换句话说,我宁愿不聪明,但如果你想成为下面的代码,可以是一个替代方案。

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

所以这是代码

动物.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

牛·

#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

鲑鱼

#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

斑马

#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

主.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;
}

除了使用宏来定义类(这更糟!),你可能没有很多事情可以做。 偶尔这样的东西可能会起作用,但我敢打赌,在某个时候,你会想要专门研究其中一种动物,导致你再次放弃宏。正是出于这个原因,我会避免使用这种特定的技术。

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

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

例如,walk()。在牛/斑马/马/猫/狗的情况下,行走的行为几乎相同。唯一真正的差异可以用数据来衡量(例如步行速度,使用多少条腿,步行的步态是多少,每步有多大?如果能够以数据驱动的方式定义行为,则只需在 Derived 类构造函数中设置这些参数,并避免需要 定制方法。以这种方式进行类设计还有其他一些好处,例如,您将有一个"Dog"类,但它能够表示 4 条腿的狗和一条 3 条腿的狗,而无需创建新类。

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