Как можно избежать включения класса, хранящегося в виде поля, в структуру данных? - PullRequest
0 голосов
/ 31 января 2020

Допустим, у меня есть класс Squad с динамическим c массивом Unit с. Я ищу способ избежать #include вставки "Unit.h" в "Squad.h" и пользователей последнего. Глобальная идея состоит в том, чтобы избегать #include s, ведущих к другим - довольно раздражающая вещь.

Я нашел простое решение, подобное этому:

// Squad.h
class Unit;
class Squad {
private:
  Unit*  units;
  size_t units_num;
public:
  void do_something_with_units(); // its implementation requires Unit to be a full type
};
// Squad.cpp
#include "Unit.h" // it's fine (and required) in Squad.cpp
void Squad::do_something_with_units() { // implements
}
// squad_user.h
// I want this whole file to be satisfied with only
// a forward-declaration of Unit (coming from Squad.h)
// provided it doesn't interact with Unit directly
void foo() {
  Squad squad;
  squad.do_something_with_units(); // works!
}

(Помещение объявленных вперед Unit в std::vector внутри Squad завершается неудачей, поскольку пользователям Squad необходимо #include Unit самим, иначе vector не может выделить.)

Вопрос номер один : допустима ли такая практика? Конечно, я не собираюсь каждый раз переписывать кишки std::vector (и других), но создание заголовка с алгоритмами, такими как template<typename T> T* reallocate(T*, size_t current, size_t needed) для более поздних #include с. cpp кажется сносным.

Вопрос номер два : есть ли лучшее решение? Я знаю об идиоме pimpl, но мне не нравятся ее частые небольшие выделения кучи.

Кстати, что мне делать с этим, когда мне нужна более сложная структура данных, такая как std :: unordered_map? Замена массива не сложна, но есть «худшие» случаи, подобные этому.

1 Ответ

1 голос
/ 01 февраля 2020

По-видимому, ключевая проблема здесь в том, что vector определен как полезный класс, обрабатывающий несколько вещей (например, копирование) автоматически. Стоимость полной автоматизации заключается в том, что тип значения (Unit) должен быть полным типом, когда определен содержащий класс (Squad). Чтобы снять эту стоимость, от автоматизации нужно отказаться. Некоторые вещи все еще автоматизированы, но теперь программист должен знать правило трех (или пяти) . (Полная автоматизация превращает это правило в правило нуля, которому тривиально следовать.)

Главное, что любой обходной путь также потребует знания правила трех, поэтому они не оказываются лучше, чем vector подход. Сначала они могут выглядеть лучше, но не после того, как все ошибки исправлены. Так что нет, не изобретайте сложные структуры данных; придерживаться vector.


является ли такая практика приемлемой?

В целом, практика приемлема , если все сделано правильно . Обычно, как правило, это не гарантировано. Кроме того, ваша реализация недопустимо неполна.

  1. Ваш класс Squad имеет необработанный указатель, который не инициализирован, что позволяет ему собирать значение мусора. Вам нужно инициализировать элементы данных (особенно если они private, следовательно, только класс может их инициализировать).

  2. Ваш необработанный указатель предназначен для владения памятью, на которую он указывает поэтому ваш класс должен следовать правилу трех (или пяти) . Ваш класс должен определить (или удалить) конструктор копирования, оператор присваивания копии и деструктор. Все эти определения (вероятно) потребуют полного объявления Unit, поэтому вы хотите, чтобы определения в вашем файле реализации наряду с do_something_with_units (то есть не были определены inline в файле заголовка).

Это придирчивость? На самом деле, нет. Устранение этих упущений приводит нас к сфере «необоснованного». Давайте посмотрим на ваше новое определение класса.

// Squad.h
class Unit;
class Squad {
private:
  Unit*  units;
  size_t units_num;
public:
  Squad();
  Squad(const Squad &);             // implementation requires Unit to be a full type
  Squad & operator=(const Squad &); // implementation requires Unit to be a full type
  ~Squad();                         // implementation requires Unit to be a full type

  void do_something_with_units();   // implementation requires Unit to be a full type
};

На этом этапе можно было бы реализовать конструктор по умолчанию в заголовочном файле (если он инициализирует units в nullptr вместо выделения памяти) , но давайте рассмотрим возможность, что нет ничего страшного в том, чтобы поместить его в файл реализации вместе с другими новыми функциями. Если вы можете принять это, то работает следующее определение класса с аналогичными требованиями к файлу реализации.

// Squad.h
#include <vector>
class Unit;
class Squad {
private:
  std::vector<Unit> units;
public:
  Squad();                          // implementation requires Unit to be a full type
  Squad(const Squad &);             // implementation requires Unit to be a full type
  Squad & operator=(const Squad &); // implementation requires Unit to be a full type
  ~Squad();                         // implementation requires Unit to be a full type

  void do_something_with_units();   // implementation requires Unit to be a full type
};

Я сделал два изменения: необработанный указатель и размер были заменены на vector, и Конструктор по умолчанию теперь требует, чтобы Unit был полным типом для его реализации. Реализация конструктора по умолчанию может быть такой же простой, как Squad::Squad() {}, при условии, что в этот момент доступно полное определение Unit.

И это, вероятно, то, чего вам не хватало. Подход vector работает, если вы предоставляете явные реализации для конструирования, уничтожения и копирования, и если вы помещаете эти реализации в свой файл реализации . Определения могут показаться тривиальными, так как компилятор делает большую работу за кулисами, но именно эти функции налагают требование, чтобы Unit был полным типом. Извлеките их определения из заголовочного файла, и план сработает.

(Вот почему комментаторы были озадачены тем, почему вы думали, что vector не будет работать. За пределами конструктора, времена, когда Для vector необходимо, чтобы Unit был полным типом, - это как раз то время, когда вашей реализации потребуется Unit, чтобы быть полным типом. Предлагаемая реализация - это дополнительная работа без дополнительных преимуществ.)

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