Использование контейнеров абстрактного класса для хранения дочерних классов

Я хочу, чтобы несколько классов наследовались от этого интерфейса:

class IPlayer {

public:
     virtual ~IPlayer() {}

     virtual void doSomething() = 0;

protected:
     std::string m_name;

};

Вот класс, который должен наследоваться от приведенного выше:

class Jack : public IPlayer {

public:
      Jack(std::string t_name)
      {
             m_name = t_name;
      }

      ~Jack() { }

      void doSomething()
      {
              /* do a bunch of stuff */
      }

};

Имейте в виду, что у нас есть другие классы, которые наследуются от IPlayer таким же образом, например Bob и Alice.

Теперь предположим, что я хотел создать контейнер для Jacks, Bobs и Alices, который позволил бы мне перегруппировать их в одной переменной. В таком состоянии для меня это невозможно, так как у IPlayer нет ctor, поэтому не может служить шаблоном для таких объектов, как векторы или списки. (по крайней мере я так понимаю)

Было бы лучше иметь класс между IPlayer и Jack, который реализовывал бы только ctor и dtor, а затем оставлял бы другие методы чисто виртуальными для использования детьми;

ИЛИ

Сделать IPlayer (и/или любой будущий интерфейс) наследником того же класса, что и выше, который реализует только ctor и dtor, просто чтобы сделать IPlayer ctorable для контейнеров?

Вы бы использовали контейнер (возможно, умных) указателей на IPlayer, как в std::vector<IPlayer*> или std::vector<std::unique_ptr<IPlayer>>. У класса IPlayer уже есть конструктор — конструктор по умолчанию; причина, по которой его экземпляр не может быть создан, заключается в том, что он абстрактен. Добавление дополнительных конструкторов не изменит этот факт.

Igor Tandetnik 08.06.2019 22:28

@IgorTandetnik Верно, это самый правильный вариант. Почему/как использование контейнера указателей позволяет обойти эту проблему?

SpectreVert 08.06.2019 22:44

Обходит какую проблему? Я не уверен, что вполне понимаю природу трудности.

Igor Tandetnik 08.06.2019 22:45

Если бы не ctor, что во внутренних функциях абстрактного класса делает его неспособным служить экземпляром для контейнеров?

SpectreVert 08.06.2019 22:50

Вы не можете создать экземпляр абстрактного класса, точка; будь то элемент контейнера или иным образом. В этом суть чисто виртуальных функций. Вы не можете иметь std::vector<IPlayer> по той же причине, по которой вы не можете писать IPlayer player; или IPlayer* player = new IPlayer;

Igor Tandetnik 08.06.2019 22:51

Правильно, теперь намного понятнее. Все это связано с моим непониманием того, что могут или не могут делать абстрактные классы. Теперь я понимаю, что могу использовать указатели или ссылки на абстрактные классы, но не могу создать объект из абстрактного класса. Спасибо за терпеливость :)

SpectreVert 08.06.2019 22:58
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

На самом деле с этими классами проблем нет. IPlayer может быть абстрактным классом, но вам никогда не нужно создавать экземпляр этого класса. Вы можете прекрасно объявить вектор указателей на IPlayers, если все элементы вектора указывают на неабстрактные подклассы.

Jack* j = new Jack("jack");
std::vector<IPlayer*> v = {j};

Этот код прекрасно работает, так как экземпляр IPlayer не создается.

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

rm1948 09.06.2019 05:07

@ rm1948 Я использовал голые указатели, чтобы упростить свой код. Конечно, ОП может использовать интеллектуальные указатели, но это его выбор, поскольку я не могу знать, какой интеллектуальный указатель оптимален для его системы. Я просто показываю, как это можно реализовать.

DSC 09.06.2019 10:09

IPlayer может иметь конструктор и фактически имеет его, предоставленный компилятором.

Вот один из подходов, при котором список игроков хранится в статическом элементе IPlayer. Но список не обязательно должен быть в IPlayer.

#include <memory>
#include <vector>

// forward declaration
class IPlayer;

using Players = std::vector<PlayerPtr>;

class IPlayer {
public:
    virtual ~IPlayer() {
    }
    static void addPlayer(IPlayer& player) {
        mPlayers.push_back(std::make_unique<IPlayer>(player));
    }
private:
    static Players mPlayers;
};

Players IPlayer::mPlayers;

class Bob : public IPlayer {
};

void run() {
    Bob bob;
    IPlayer::addPlayer(bob);
}

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