По-видимому, ключевая проблема здесь в том, что vector
определен как полезный класс, обрабатывающий несколько вещей (например, копирование) автоматически. Стоимость полной автоматизации заключается в том, что тип значения (Unit
) должен быть полным типом, когда определен содержащий класс (Squad
). Чтобы снять эту стоимость, от автоматизации нужно отказаться. Некоторые вещи все еще автоматизированы, но теперь программист должен знать правило трех (или пяти) . (Полная автоматизация превращает это правило в правило нуля, которому тривиально следовать.)
Главное, что любой обходной путь также потребует знания правила трех, поэтому они не оказываются лучше, чем vector
подход. Сначала они могут выглядеть лучше, но не после того, как все ошибки исправлены. Так что нет, не изобретайте сложные структуры данных; придерживаться vector
.
является ли такая практика приемлемой?
В целом, практика приемлема , если все сделано правильно . Обычно, как правило, это не гарантировано. Кроме того, ваша реализация недопустимо неполна.
Ваш класс Squad
имеет необработанный указатель, который не инициализирован, что позволяет ему собирать значение мусора. Вам нужно инициализировать элементы данных (особенно если они private
, следовательно, только класс может их инициализировать).
Ваш необработанный указатель предназначен для владения памятью, на которую он указывает поэтому ваш класс должен следовать правилу трех (или пяти) . Ваш класс должен определить (или удалить) конструктор копирования, оператор присваивания копии и деструктор. Все эти определения (вероятно) потребуют полного объявления 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
, чтобы быть полным типом. Предлагаемая реализация - это дополнительная работа без дополнительных преимуществ.)