Операторы моего производного класса не работают должным образом

Я сделал этот код, который имеет 4 класса. Класс SpecialCharacter имеет 2 базы: Hero и Enemy, оба из которых имеют Person в качестве базового класса.

#include <iostream>

class Person {
private:
    char* name;
    int level;
public:
    Person() {
        name = nullptr;
        level = 0;
    }

    Person(char* name, int level) {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
        this->level = level;
    }

    Person(const Person& p) {
        name = new char[strlen(p.name) + 1];
        strcpy(name, p.name);
        level = p.level;
    }

    virtual ~Person() {
        delete[] name;
    }

    Person& operator=(const Person& p) {
        if (this != &p) {
            delete[] name;

            name = new char[strlen(p.name) + 1];
            strcpy(name, p.name);

            level = p.level;
        }
        return *this;
    }

    bool operator<(const Person& p) const {
        return strcmp(this->name, p.name) < 0;
    }

    friend std::istream& operator>>(std::istream& is, Person& p) {
        char nameTemp[100];
        is >> nameTemp >> p.level;

        delete[] p.name;
        p.name = new char[strlen(nameTemp) + 1];
        strcpy(p.name, nameTemp);

        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const Person& p) {
        os << "name " << p.name << "\n";
        os << "level " << p.level << "\n";

        return os;
    }
};

class Hero : virtual public Person {
private:
    char* ability;
public:
    Hero() : Person() {
        ability = nullptr;
    }

    Hero(char* name, int level, char* ability) : Person(name, level) {
        this->ability = new char[strlen(ability) + 1];
        strcpy(this->ability, ability);
    }

    Hero(const Hero& h) : Person(h) {
        this->ability = new char[strlen(h.ability) + 1];
        strcpy(this->ability, h.ability);
    }

    virtual ~Hero() {
        delete[] ability;
    }

    Hero& operator=(const Hero& h) {
        if (this != &h) {
            Person::operator=(h);
            delete[] ability;
            this->ability = new char[strlen(h.ability) + 1];
            strcpy(this->ability, h.ability);
        }
        return *this;
    }

    friend std::istream& operator>>(std::istream& is, Hero& h) {
        is >> static_cast<Person&>(h);

        char tempAbil[100];
        is >> tempAbil;

        delete[] h.ability;
        h.ability = new char[strlen(tempAbil) + 1];
        strcpy(h.ability, tempAbil);

        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const Hero& h) {
        os << static_cast<const Person&>(h);

        os << "ability " << h.ability << "\n";

        return os;
    }
};

class Enemy : virtual public Person {
protected:
    char* type;
public:
    Enemy() : Person() {
        type = nullptr;
    }

    Enemy(char* name, int level, char* type) : Person(name, level) {
        this->type = new char[strlen(type) + 1];
        strcpy(this->type, type);
    }

    Enemy(const Enemy& e) : Person(e) {
        this->type = new char[strlen(e.type) + 1];
        strcpy(this->type, e.type);
    }

    Enemy& operator=(const Enemy& e) {
        if (this != &e) {
            Person::operator=(e);
            delete[] type;
            type = new char[strlen(e.type) + 1];
            strcpy(type, e.type);
        }
        return *this;
    }

    virtual ~Enemy() {
        delete[] type;
    }

    friend std::istream& operator>>(std::istream& is, Enemy& e) {
        is >> static_cast<Person&>(e);

        char temptype[100];
        is >> temptype;

        e.type = new char[strlen(temptype) + 1];
        strcpy(e.type, temptype);

        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const Enemy& e) {
        os << static_cast<const Person&>(e);

        os << "type " << e.type << "\n";

        return os;
    }
};

class SpecialCharacter : public Hero, public Enemy {
private:
    int team;
public:
    SpecialCharacter() : Person(), Hero(), Enemy() {
        team = 0;
    }

    SpecialCharacter(char* name, int level, char* ability, char* type, int team) : Person(name, level), Hero(name, level, ability), Enemy(name, level, type) {
        this->team = team;
    }

    SpecialCharacter(const SpecialCharacter& sp) : Person(sp), Hero(sp), Enemy(sp) {
        this->team = sp.team;
    }

    SpecialCharacter& operator=(const SpecialCharacter& sp) {
        if (this != &sp) {
            Hero::operator=(sp);
            this->type = sp.type;
            this->team = sp.team;
        }
        return *this;
    }

    friend std::istream& operator>>(std::istream& is, SpecialCharacter& sp) {
        is >> static_cast<Hero&>(sp);
        char temptype[100];
        is >> temptype;

        sp.type = new char[strlen(temptype) + 1];
        strcpy(sp.type, temptype);
        is >> sp.team;

        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const SpecialCharacter& sp) {
        os << static_cast<const Hero&>(sp);
        os << "type " << sp.type << "\n";

        os << "team number " << sp.team << "\n";

        return os;
    }
};

int main()
{
    Person** pers = new Person * [5];
    int i = 0, r;

    while (i < 5) {
        r = rand() % 3;

        if (r == 0) {
            std::cout << "Enter info for hero\n";
            Hero* e = new Hero();
            std::cin >> *e;
            pers[i] = e;
        }
        else if (r == 1) {
            std::cout << "Enter info for enemy\n";
            Enemy* in = new Enemy();
            std::cin >> *in;
            pers[i] = in;
        }
        else {
            std::cout << "Enter info for special character\n";
            SpecialCharacter* ps = new SpecialCharacter();
            std::cin >> *ps;
            pers[i] = ps;
        }

        i++;
    }

    for (int j = 0; j < 5; j++) {
        Hero* e = dynamic_cast<Hero*>(pers[j]);
        if (e != nullptr) {
            std::cout << *e;
            continue;
        }

        Enemy* in = dynamic_cast<Enemy*>(pers[j]);
        if (in != nullptr) {
            std::cout << *in;
            continue;
        }

        SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
        if (ps != nullptr) {
            std::cout << *ps;
            continue;
        }
    }
}

Я попытался перегрузить операторы << и >> для SpecialCharacter, используя static_cast, в результате чего он сначала считывает/распечатывает объекты как тип Hero, после чего считывает/распечатывает значения членов Enemy и SpecialCharacter.

Однако, когда я тестировал этот код в main(), он отображает только name, level и abilities для объектов SpecialCharacter, то есть членов классов Person и Hero.

Может ли кто-нибудь помочь мне понять, почему это происходит? Это из-за того, как я использовал dynamic_cast в цикле for из main()? Или я неправильно перегрузил операторы SpecialCharacter?

Переключитесь на std::string для текста. Используйте std::string::c_str() для конвертации в char * только при необходимости.

Thomas Matthews 11.06.2024 00:38

В Person не управляйте этой памятью вручную. std::string сделает код намного проще.

Pete Becker 11.06.2024 00:38

Если вы используете другой стандарт кодирования, в котором имена членов отличаются от имен параметров, вы можете исключить весь синтаксис this->. Дополнительный набор текста увеличивает вероятность появления проблем.

Thomas Matthews 11.06.2024 00:39

К вашему сведению, C++ отличается от других языков: вам не нужно размещать объекты в динамической памяти, чтобы их использовать. Относитесь к объектам как к целым числам: просто объявите их и используйте. Предпочитаю держаться подальше от динамического распределения памяти; используйте экономно и только при необходимости.

Thomas Matthews 11.06.2024 00:42

Примечания: используйте списки инициализации базовых членов в своих конструкторах. Они более эффективны, чем задания, которые вы используете. В качестве бонуса они позволяют опускать this->. Согласен с @Thomas Matthews относительно std::string. Есть ли причина, по которой вы его не используете?

tbxfreeware 11.06.2024 00:51

Вы можете удалить SpecialCharacter класс. Рефакторинг члена team в класс Person. Присвойте ему значение «не в команде». Удаление класса SpecialCharacter также устраняет проблему множественного наследования.

Thomas Matthews 11.06.2024 01:01

К вашему сведению: использование std::string уменьшает объем написанного вами кода почти до вдвое.

PaulMcKenzie 11.06.2024 03:16

Если вам не разрешено использовать std::string, то есть из-за ограничений проекта, вам следует написать свой собственный класс строк, чтобы исключить весь этот ненужный дублирующийся код.

Remy Lebeau 11.06.2024 05:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

SpecialCharacter происходит от Hero. В цикле печати ваша первая dynamic_cast проверка на Hero* будет успешной для SpecialCharacter объектов, а затем вы continue перейдете к следующей итерации, так что у вас никогда не будет возможности распечатать члены type и team этих объектов.

Ваш цикл печати должен проверять SpecialCharacter* первым, а не последним.

for (int j = 0; j < 5; j++) {

    // DO THIS HERE!
    SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
    if (ps != nullptr) {
        std::cout << *ps;
        continue;
    }

    Hero* e = dynamic_cast<Hero*>(pers[j]);
    if (e != nullptr) {
        std::cout << *e;
        continue;
    }

    Enemy* in = dynamic_cast<Enemy*>(pers[j]);
    if (in != nullptr) {
        std::cout << *in;
        continue;
    }

    /* NOT HERE !
    SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
    if (ps != nullptr) {
        std::cout << *ps;
        continue;
    }
    */
}

@Remy Lebeau правильно определил причину конкретной проблемы, о которой спрашивал ОП, и таким образом заслужил мой положительный голос. Обязательно прочтите его ответ.

Однако здесь есть более серьезная проблема. ОП имеет виртуальную иерархию, которая никогда не используется. Хотя существует множество приведения вверх и вниз, ни в одном коде OP нет виртуальных функций. Всего приведения можно избежать, правильно используя виртуальные методы.

В частности, виртуальная функция put решит проблему, поставленную в исходном вопросе. В каждом классе дружественная функция operator<< должна вызывать виртуальный метод put и позволить vtable разобраться, какая put функция вызывается.

Ошибка в ОП не в том, что даункаты проверялись не в том порядке (хотя они были). Ошибка в том, что в первую очередь нужно было использовать понижающие приведения.

class Person {

    //...

    friend std::ostream& operator<<(std::ostream& ost, const Person& p) {
        p.put(ost);
        return ost;
    }
protected:
    virtual void put(std::ostream& ost) const {
        ost << "name " << name 
            << "\nlevel " << level 
            << '\n';
    }
};
class Hero : virtual public Person {

    //...

    friend std::ostream& operator<<(std::ostream& ost, const Hero& h) {
        h.put(ost);
        return ost;
    }
protected:
    void put(std::ostream& ost) const override {
        Person::put(ost);
        ost << "ability " << ability << '\n';
    }
};
class Enemy : virtual public Person {

    //...

    friend std::ostream& operator<<(std::ostream& ost, const Enemy& e) {
        e.put(ost);
        return ost;
    }
protected:
    void put(std::ostream& ost) const override {
        Person::put(ost);
        ost << "type " << type << '\n';
    }
};
class SpecialCharacter : public Hero, public Enemy {

    //...

    friend std::ostream& operator<<(std::ostream& ost, const SpecialCharacter& sp) {
        sp.put(ost);
        return ost;
    }
protected:
    void put(std::ostream& ost) const override {
        Hero::put(ost);
        ost << "type " << type 
            << "\nteam number " << team 
            << '\n';
    }
};

Выходные данные пробного прогона

С этими изменениями порядок, в котором вы проверяете понижения, не имеет значения. Этот запуск был выполнен с использованием неисправленной версии функции main из ОП.

// from function `main` in the OP:
for (int j = 0; j < 5; j++) {
    Hero* e = dynamic_cast<Hero*>(pers[j]);     // <---------- Order does not matter
    if (e != nullptr) {
        std::cout 
            << typeid(*pers[j]).name() << '\n'  // <---------- Operator `typeid`
            << *e << '\n';
        continue;
    }

    Enemy* in = dynamic_cast<Enemy*>(pers[j]);
    if (in != nullptr) {
        std::cout
            << typeid(*pers[j]).name() << '\n'
            << *in << '\n';
        continue;
    }

    SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
    if (ps != nullptr) {
        std::cout
            << typeid(*pers[j]).name() << '\n'
            << *ps << '\n';
        continue;
    }
}

Единственным существенным изменением функции main был вызов typeid(*pers[j]).name(), чтобы в выводе отображалось имя класса.

Выход

Каждый класс был напечатан с правильным набором переменных-членов. Таким образом, для каждого вызывался правильный метод put.

Enter info for special character
sp1 1 sp1 sp1 1

Enter info for hero
h1 1 h1

Enter info for hero
h2 2 h2

Enter info for special character
sp2 2 sp2 sp2 2

Enter info for enemy
e1 1 e1

class SpecialCharacter
name sp1
level 1
ability sp1
type sp1
team number 1

class Hero
name h1
level 1
ability h1

class Hero
name h2
level 2
ability h2

class SpecialCharacter
name sp2
level 2
ability sp2
type sp2
team number 2

class Enemy
name e1
level 1
type e1

Избавьтесь от уныния

Конечно, вся суть заключалась в том, чтобы вообще избегать использования каких-либо понижений.

Предыдущий цикл теперь можно заменить следующим:

// Corrected loop from function `main`:
for (int j = 0; j < 5; j++) {
    std::cout
        << typeid(*pers[j]).name() << '\n'
        << *pers[j] << '\n';
}

Дополнительные примечания

ОП заслуживает похвалы за написание настоящих классов «Правила трех». Но затем в функции main он вызывает new и new[] без каких-либо соответствующих вызовов delete и delete[]. Это немного придирка, потому что операционная система восстановит утекшую память, как только программа завершится, но я бы предпочел, чтобы std::vector использовалось в функции main, возможно, с std::unique_ptr для управления указателями.

std::vector<std::unique_ptr<Person>> pers;

Кроме того, когда вам запрещено использовать std::string, рекомендация Реми Лебо написать свой собственный String класс будет хорошей. Это переместило бы элементы RAII в класс String и тем самым упростило бы остальную часть кода.

Другие вопросы по теме