Метод абстрактного класса, используемый производным классом

У меня есть абстрактный базовый класс, который выглядит так:

class AbstractClass {
public:
    virtual ~AbstractClass() = 0 {}
    std::string GetName() const { return m_Name; }
private:
    std::string m_Name;
};

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

class DerivedClass1 : public AbstractClass{
public:
    DerivedClass1() = default;
    ~DerivedClass1() = default;
private:
    std::string m_Name = "DerivedClass1";
};


int main() {
    DerivedClass1 class1;
    std::cout << class1.GetName();

    return 0;
}

Я не хочу переопределять GetName() каждый раз, когда я получаю класс, возможно ли это? Редактировать: У меня ошибка компоновщика. Ошибка LNK2019.

да это возможно, просто сделайте это. Непонятно, в чем проблема/вопрос. Если вы столкнулись с проблемами, вы должны сообщить

463035818_is_not_a_number 09.04.2019 15:24

чистый абстрактный деструктор?

Marek R 09.04.2019 15:25

Упс. Я забыл добавить, что получаю LinkerError... ErrorCode 2019.

user7972945 09.04.2019 15:26

гул после проверки вашего кода я понял, в чем ваша «проблема». Кажется, у вас сложилось впечатление, что вы можете переопределить членов класса. Прочтите о проблема ху и объясните, чего вы на самом деле хотите достичь

463035818_is_not_a_number 09.04.2019 15:26

@MarekR Да, я где-то читал, что это возможно, и я явно не хочу делать GetName виртуальным.

user7972945 09.04.2019 15:27

@HenningWilmer, это был риторический вопрос, он объясняет проблему со ссылками, видеть это

Marek R 09.04.2019 15:30

Во всяком случае, я вижу, что люди прыгают по этой проблеме с dtor и не замечают, что в вашей строке кода совпадает имя класса. Итак, похоже, что это проблема XY (я проголосовал за пользователя 463035818), и я жду возведения в степень проблемы X.

Marek R 09.04.2019 15:33

Стоит упомянуть (хотя, вероятно, это не ваша основная проблема), что DerivedClass1 class1; std::cout << class1.GetName(); не использует динамическую диспетчеризацию (т.е. не заботится о том, являются ли ваши функции virtual/полиморфными/переопределенными где-либо). Вам нужен указатель или ссылка для правильного использования виртуальных функций (прочитайте о динамическом и статическом типах).

Max Langhof 09.04.2019 15:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
70
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы получаете ошибку ссылки, потому что деструктор для AbstractClass должен быть определен, даже если он пуст.

AbstractClass::~AbstractClass()
{
  // Compulsory virtual destructor definition,
  // even if it's empty
}

ПРЯМОЙ ЭФИР на Wandbox

Что касается переопределения getName: вам не нужно. Если вы не предоставляете реализацию в производном классе, используется унаследованная.

Хорошо. Но я все еще хочу, чтобы класс был абстрактным. Как мне этого добиться?

user7972945 09.04.2019 15:30

Это уже абстрактно. если вы попытаетесь объявить объект типа AbstractClass , вы увидите, что ошибка компилятора будет довольно ясной.

Davide Spataro 09.04.2019 15:32

@HenningWilmer Абстрактность просто означает, что у вас есть хотя бы одна чистая виртуальная функция. Это не то, что кодирует требует, это то, что бывает, потому что что-то в классе должно быть абстрактным. Основываясь на текущих потребностях вашего класса (метод GetName и ничего больше), ваш класс еще не является абстрактным, но, вероятно, станет таковым (при необходимости) по мере добавления реальной функциональности.

Sneftel 09.04.2019 15:39

@DavideSpataro Хорошо, спасибо! Это почти сработало для меня. Я больше не получаю ошибку, но и не получаю вывод. Ах, я вижу, потому что m_Name класса AbstactClass пусто..

user7972945 09.04.2019 15:46
Ответ принят как подходящий

Используйте только одно имя в базовом классе и конструктор с параметром:

class AbstractClass{
public:
   AbstractClass(const std::string& name) : m_Name(name){}
   std::string GetName() const { return m_Name; }
private:
   std::string m_Name;
};



DerivedClass1 : public AbstractClass{
public:
   DerivedClass() : AbstractClass("DerivedClass1") {}
};


int main(){
   DerivedClass1 class1;
   std::cout << class1.GetName();

    return 0;
}

Кажется, нет причин делать базовый класс абстрактным, но если вам делать это нужно, даже чистый виртуальный деструктор должен иметь определение, иначе вы получите ошибку компоновщика, потому что это необходимо при уничтожении производных объектов. Кроме того, если бы деструктора не существовало, когда бы m_Name был уничтожен?

class Abstract
{
public: 
    virtual ~Abstract() = 0;
};

Abstract::~Abstract() {}

Это создает класс, который не может быть создан, но чьи производные классы все еще могут быть уничтожены.

Это не то, как вы «переопределяете» GetName(). Вы можете сделать GetName() виртуальным и переопределить его в своих производных классах:

class AbstractClass {
public:
   virtual ~AbstractClass() = default;
   virtual std::string GetName() const { return "AbstractClass"; }
private:
   std::string m_Name;
};

а также:

class DerivedClass1 : public AbstractClass {
public:
   DerivedClass() = default;
   std::string GetName() const override { return "DerivedClass1"; }
};

Или вы можете установить m_Name в своих производных классах, передав его конструктору базового класса:

class AbstractClass {
public:
   AbstractClass(const std::string& name) : m_Name(name) {}
   virtual ~AbstractClass() = default;
   std::string GetName() const { return m_Name; }
protected: // protected not private
   std::string m_Name;
};

а также:

class DerivedClass1 : public AbstractClass {
public:
   DerivedClass() : AbstractClass("DerivedClass1") {}
};

Или вы можете установить его в конструкторе производного класса:

class AbstractClass {
public:
   virtual ~AbstractClass() = default;
   std::string GetName() const { return m_Name; }
protected: // protected not private
   std::string m_Name;
};

а также:

class DerivedClass1 : public AbstractClass {
public:
   DerivedClass() : AbstractClass() { m_Name = "DerivedClass1"; }
};

Код предполагает, что проблема заключается в том, как получить имя класса? Но это не ясно указано в вопросе (проблема XY)

Как обрабатывать имя класса?

Вы можете использовать RTTI:

class ClassName {
public:
    virtual ~ClassName() {} // just to enable RTTI for all decendants

    std::string getClassName() {
        return typeid(*this).name();
    }
};

https://wandbox.org/permlink/LvPdA37arMr0LFQW

Но, как вы можете видеть, он добавляет дополнительный префикс (это зависит от компилятора). boost может очистить его:

https://wandbox.org/permlink/8XiB7yVOM0wYVxpl

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