Ваш код не соответствует вашему описанию (например, D
не наследуется от B
в вашем коде). Существует также ряд ошибок компилятора, происходящих с небрежным синтаксисом, таких как отсутствие ключевого слова class
в определениях вашего класса. Тем не менее, я полагаю, что достаточно отправной точки для работы.
В целом, я подозреваю, что в вашем дизайне есть недостатки, которые следует устранить, особенно в отношении classB::init()
. Тем не менее, вопрос не имеет (и, возможно, не должен) иметь достаточный контекст, чтобы определить это. Поэтому я проведу ваши занятия в порядке, который имеет для меня смысл.
Давайте начнем с classC
, что для меня выглядит отправной точкой для этой установки. То, что у вас есть, выглядит разумно. У класса есть открытый конструктор, принимающий параметр, который можно получить только из classB
, что почти соответствует вашему описанию. Ваше описание упоминает два параметра, а не только один, но это, вероятно, тривиальная разница. Просто попробуйте пометить конструктор explicit
.
Следующий класс, на который я посмотрю, - classA
. В вашем описании говорится о необходимости «объекта classC в classA », однако в вашей реализации это, похоже, упущено. С другой стороны, classA::doSomething
ссылается на неопределенную переменную (classC_object
). Мой вывод состоит в том, что classC_object
должен был быть членом A
и типа classC
. Поэтому я добавлю это поле к определению вашего класса.
class classA
{
classC classC_object;
public:
explicit classA(classC classC_object) : classC_object(std::move(classC_object)) {}
void doSomething()
{
classC_object.someMethod();
}
};
Как в вашем описании, так и в вашем коде сказано, что classA
не может создать объект classC
. Я хотел бы указать, однако, что он мог бы, если бы дал some_type_specificly_created_in_classB
объект. Естественно, это не будет хорошим вариантом, если нарушит инкапсуляцию.
Теперь мы подошли к classB
, где мы столкнулись с загадкой. Вы объявили член типа classC
, но этот член не может быть создан до тех пор, пока не будет вызван init()
. Не ясно, как решить эту проблему без лучшего контекста. Из вашего описания я думал, что параметры конструкции будут храниться в объекте classB
, а не во всем объекте classC
. Является ли это оптимизацией, чтобы избежать многократного создания объекта classC
? Нужен ли каждому classB
объекту classC
объект по другим причинам? Если оба ответа «нет», вы можете заменить classC classC_object
на some_type_specificly_created_in_classB arg
и добавить функцию-член, которая создает и возвращает объект classC
на основе arg
.
Если естьпричина для init()
для создания объекта classC
, это может быть сделано. Однако сначала нужно избавиться от строки «ничего не делать», которая создает local объект с именем classC_object
- это не влияет на элемент с тем же именем, и фактически это объявление скрывает элемент (см. Также Обнаружение теневого копирования переменных-членов ). Как только это будет сделано, вы можете изменить свой элемент данных на некоторый указатель. Для этого указателя в конструкторе будет установлено значение null, что означает, что вам также необходимо решить, что делать, если объект classC
запрашивается до вызова init()
. Я пойду с вызовом init()
в этот момент. (Я не знаю, правдоподобно ли это, так как я не знаю, почему init()
не вызывается из конструктора. Он не принимает параметров, поэтому кажется безопасным вызывать.)
#include <memory>
class classB
{
std::unique_ptr<classC> classC_pointer; // <-- pointer, and private.
public:
classB(int argc, char** argv) : classC_pointer()
{
// do something
// ** Why is init() not part of this constructor? **
}
void init()
{
some_type_specificly_created_in_classB arg;
std::make_unique<classC>(arg).swap(classC_pointer); // <-- construct classC object
}
const classC & getC()
{
if ( !classC_pointer ) // If the pointer is null
init();
return *classC_pointer;
}
};
СноваЕсть и другие варианты, что делать здесь. В частности, я бы посмотрел, можно ли выполнить функциональность init()
в конструкторе. Если функциональность может быть вызвана из списка инициализаторов, возможно, можно вернуться к наличию элемента classC
вместо указателя. Указатель подход может быть одной из более сложных возможностей. Если вам нужен такой подход, вы можете пересмотреть свой дизайн;мог бы быть лучший способ разделить задачи.
Наконец, есть classD
. В вашем описании говорится, что это должно наследоваться от classB
, и я бы придерживался этого, поскольку это облегчает обеспечение инициализации в требуемом порядке. (Базовые классы инициализируются перед членами.) Больше ничего не нужно делать с вашим определением, кроме как убедиться, что параметры переданы classB
. Что касается стиля, я бы дал classA_object
(которое было бы более точно названо classA_pointer
) значение в списке инициализатора.
class classD : public classB
{
public:
classD(int argc, char** argv) : classB(argc, argv),
classA_object{nullptr}
{
init(); // <-- You did say this would be called early enough!
classA_object = new classA(getC());
}
private:
classA* classA_object;
};
Обратите внимание, что это выглядит как единственная причина для создания classA_object
указатель таков, что init()
может быть вызван во времени. Если бы эту функциональность можно было перенести в конструктор classB
, то, возможно, classA_object
можно было бы заменить на объект classA
вместо указателя classA
? На самом деле, подход, который я использовал в getC()
, позволил бы это в любом случае.
class classD : public classB
{
public:
classD(int argc, char** argv) : classB(argc, argv),
classA_object(getC())
{}
private:
classA classA_object; // <-- no longer a pointer
};