Avoiding repetitive sub-class definitions in C++

感情迁移 提交于 2021-02-11 08:58:30

问题


I am new to C++ classes, and I have a question about defining multiple sub-classes of an abstract type/interface which would have identical definitions.

Take the following example which might appear in a header file with 3 sub-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

Declaring the sub-classes like this seems repetitive and potentially error prone. Because the class definitions are identical except for the name of the class, is there a more efficient way to declare the animal sub-classes in bulk?

In the context I am working in each animal would have a completely independent implementation in separate .cpp files.

Please let me know if i'm approaching this completely wrong. Any help would be greatly appreciated.


回答1:


Short of using a macro to define the classes (which is even worse!), there probably isn't a great deal you can do. Occasionally stuff like this might work, but I'd wager that at some point, you'd want to specialise one of the animals, leading to you ditching the macro again. It's for that reason I'd avoid that particular technique.

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

Generally speaking, try to move as much of the duplicated class methods & data into the base class to minimise the amount of code duplication. This may require a slight change in the way you think about the problem though....

For example, walk(). In the case of a Cow/Zebra/Horse/Cat/Dog, the act of walking is pretty much identical. The only real differences can be measured with data (e.g. the walk speed, how many legs are used, what is the gait of the walk, how large is each stride?). If you can define the behaviour in a data-driven fashion, you would just need to set those parameters in the Derived class constructor, and avoid the need for customised methods. Approaching the class design this way has a few other benefits, for example you'd have a single 'Dog' class, but it would be able to represent a 4 legged dog, and a 3 legged dog, without needing to create a new class.

That's usually the approach I'd recommend anyway...




回答2:


It is indeed error prone when you don't make use of the override keyword for each overridden virtual class member function.

Instead of declaring the derived class function like this

bool someFunction();

you can/should declare it like this

bool someFunction() override;

In this way, you would get a compilation error if the declaration doesn't match the base class signature. Without it, you would have a perfectly good compilable program but with a behaviour bug.

Other than that, your strategy is fine, and is the way to do handle abstract functions.




回答3:


I'm writing another answer as an alternative solution. Actually, If i faced with same 'issue' or 'problem', i would not declare as bulk, I would just create zebra.h, zebra.cpp, inherits from Animal and declare/define all members individually. In other words i would prefer not to be clever but if you want to be the piece of codes below could be an alternative.

Indeed, you just want to create a class declaration from a template. That's what template is doing. It is possible to mimic same behaviour with MACROs but I would prefer template rather than MACRO because it is what Bjarne did.

So here is the code

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


来源:https://stackoverflow.com/questions/60256943/avoiding-repetitive-sub-class-definitions-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!