Как избежать повторяющихся определений подклассов в C ++ - PullRequest
1 голос
/ 17 февраля 2020

Я новичок в классах 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 файлах.

Пожалуйста, дайте мне знать, если я подхожу к этому совершенно неправильно. Любая помощь будет принята с благодарностью.

Ответы [ 3 ]

5 голосов
/ 17 февраля 2020

Это действительно подвержено ошибкам, когда вы не используете ключевое слово override для каждой переопределенной функции-члена виртуального класса.

Вместо того, чтобы объявлять функцию производного класса, как это

bool someFunction();

, вы можете / должны объявить ее следующим образом

bool someFunction() override;

Таким образом, вы получите компиляцию ошибка, если объявление не соответствует сигнатуре базового класса. Без него у вас была бы очень хорошая скомпилируемая программа, но с ошибкой поведения.

Кроме этого, ваша стратегия хороша и является способом обработки абстрактных функций.

2 голосов
/ 17 февраля 2020

Я пишу другой ответ в качестве альтернативного решения. На самом деле, если бы я столкнулся с одной и той же «проблемой» или «проблемой», я бы не объявил ее объемной, я просто создал бы zebra.h, zebra.cpp, наследовал бы от Animal и объявил / определил бы всех членов по отдельности. Другими словами, я бы предпочел не быть умным, но если вы хотите, чтобы фрагмент кода ниже мог бы стать альтернативой.

Действительно, вы просто хотите создать объявление класса из шаблона. Вот что делает template. Можно имитировать c такое же поведение с MACRO с, но я бы предпочел template, а не MACRO, потому что это то, что сделал Бьярне.

Так вот код

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;
}
1 голос
/ 17 февраля 2020

Если не использовать макрос для определения классов (что еще хуже!), Вы вряд ли что-то сможете сделать. Иногда такие вещи могут сработать, но я держу пари, что в точке около вы захотите специализировать одно из животных, что приведет к тому, что вы снова бросите макрос. По этой причине я бы избегал этой конкретной техники.

#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 и избежать необходимости настраиваемых методов. Подход к дизайну класса таким способом имеет несколько других преимуществ, например, у вас будет один класс «Собака», но он сможет представлять собаку 4-х и 3-х ног без необходимости создавать новый класс. ,

Обычно такой подход я бы рекомендовал в любом случае ...

...