Наблюдение. Конструктор ClassMain должен вызвать Init
прежде чем он сможет создать переменную-член a
. Поскольку ClassA
не имеет конструктора по умолчанию, код не компилируется.
ClassA
{
public:
// This class has no default constructor
ClassA(...){}
};
class ClassMain
{
public:
ClassMain(...) {
Init(...);
a = ClassA(...); // error: ClassA has no default constructor
// a has to been constructed after the Init is called!
}
ClassMain(...) {
Init(...);
call other functions
a = ClassA(...);
}
private:
// initialize environment
void Init(...) {}
private:
ClassA a;
};
Вопрос> Простым решением является предоставление конструктора по умолчанию для ClassA
. Однако я хотел бы знать, есть ли лучшее решение для решения проблемы выше?
Очевидным решением является вызов Init()
из списка инициализаторов раннего члена или базового класса. Как только этот подобъект построен, его результаты могут быть переданы конструкторам других подобъектов. Например, при определении классов потоков я обычно частным образом наследую от виртуальной базы, содержащей буфер потока:
struct somebuf_base {
somebuf sbuf;
// ...
};
class somestream
: private virtual somebuf_base
, public std::ostream
{
public:
somestream(someargs)
: somebuf_base(someargs)
, std::ostream(&this->sbuf) {
}
// ...
};
Так как базовые классы построены в том порядке, в котором они появляются, а в виртуальных базах до не виртуальных баз, сначала строится базовый класс, содержащий sbuf
. Его конструктор заменяет вашу функцию Init()
.
При использовании C++ с версией 2011 года вы также можете использовать конструкторы пересылки для обмена логикой между несколькими конструкторами.
Лучшее решение - не требовать функции Init
вообще. Вы пытаетесь изобретать конструкторы и нарушать их дизайн в процессе.
Если Init
выполняет слишком много работы для конструктора, то сделайте это за пределами и передайте результирующие ресурсы в ClassMain
в качестве аргумента конструктора; обратите внимание, как вы все равно выполняете всю работу в области конструктора, тем самым не получая ничего заметного из-за правильной инициализации.
Конечно, если вы должны выполнить массу работы до инициализации, и вы не можете передать в a
ClassA&
с внешней стороны и инициализируете от этого, то вы просто собираетесь иметь быть косвенным участником. a
Существует одно неприятное обходное решение, которое вы можете использовать: на самом деле Init
является базовым конструктором...
Init
заключается в том, что класс ClassMain
имеет перегруженные конструкторы, каждый из которых вызывает одну и ту же процедуру Init
. Не стоит позволять пользователю выполнять работу Init
потому что содержимое Init
должно быть скрыто от пользователя.
Легче взять указатель на ClassA; Таким образом, вы можете создавать его, когда захотите. (После init())
Если вы использовали указатель, не забудьте реализовать виртуальный деструктор и освободить выделенную память для ClassA * a
Если вы абсолютно должны вызвать некоторую функцию в начале вашего конструктора и не можете поместить эту настройку в какой-либо базовый класс или ранний элемент, вы можете использовать этот уродливый трюк:
ClassMain::ClassMain(int main_param)
: a( (Init(init_arg), class_a_arg1), class_a_arg2 )
{
}
В этом случае: Нет, мы не можем этого избежать.
Причина в том, что при вызове Init
или любой другой функции-члена вам гарантируется язык, на котором находится объект, в котором вы находитесь. Поскольку a
является членом ClassMain
он должен быть сконструирован до ClassMain
будет вызвана любая функция ClassMain
.
Единственный шанс, что вы здесь, - это реорганизовать код.
a
std::unique_ptr<ClassA> a;
и затем динамически выделить его послеInit()
.