Как явно инициализировать член, у которого нет конструктора по умолчанию? - PullRequest
0 голосов
/ 19 октября 2019

Я нарисовал очень простой график, чтобы следовать моему описанию ниже, надеюсь, это поможет: enter image description here

Моя проблема: мне нужен объект classC в класс А . Проблема заключается в том, что classC требуется два аргумента в конструкторе, который может быть создан только в classB (после вызова метода init в classB , но сейчас предположим, чтоэтот метод инициализации вызывается достаточно рано. Кроме того, у меня есть classD , который наследуется от classB (который содержит два необходимых аргумента). И, наконец, мне также нужен объект classA в classD .

Как уже говорилось, мне нужен объект classC в classA . Я не могу простосоздайте его непосредственно в classA , поскольку я не смог бы предоставить необходимые аргументы (которые создаются в classB ). Моя идея заключалась в создании объекта classC в classB (поскольку это единственный способ (я полагаю) инициализировать объект аргументами). Затем, поскольку мне нужен объект classA в classD и classD наследуется от classB , я думал, что могу взять объект classC (созданный в classB ) в classD (должно быть возможно из-за наследования, не так ли?) и передать его в classA в качестве аргумента своего конструктора. (Я не хочу, чтобы classA наследовал от classD.)

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

РЕДАКТИРОВАТЬ

Спасибо за все ответы! Я постарался максимально упростить код. Я получаю ошибку, что конструктор должен явно инициализировать член classC (см. Комментарий classA.cpp и classB.cpp). Но необходимые аргументы для инициализации объекта classC создаются во время вызова функции init в classB. В чем здесь проблема, т.е. почему я получаю эти ошибки? Я подумал, что когда я передаю объект класса в качестве аргумента для конструктора другого класса, мне не нужно снова создавать экземпляр класса? Любая помощь очень ценится!

Класс A

classA.h

#include <classC.h>

classA
{
    public:
        explicit classA(classC classC_object);
        void doSomething();
}

classA.cpp

#include <classA.h>

classA::classA(classC classC_object) // Error: constructor for classC must explicitly initialize the member classC_object which does not have a default constructor
{
}

void classA::doSomething()
{
    classC_object.someMethod();
}

класс B

classB.h

classB
{
    public:
        classB(int argc, char** argv);

        void init();

        classC classC_object;
}

classB.cpp

#include <classB.h>

classB::classB(int argc, char** argv) // Error: constructor for classB must explicitly initialize the member classC_object which does not have a default constructor
{
}

void classB::init()
{
    some_type_specificly_created_in_classB arg;
    classC classC_object(arg);
}

Класс C

classC.h

classC
{
    public:
        classC(const some_type_specificly_created_in_classB& arg);
        // some methods I want to access in classA
        void someMethod();
}

Класс D

classD.h

#include <classB.h>
#include <classA.h>

classD
{
    public:
        classD(int argc, char** argv);

    private:
        classB classB_object;
        classA* classA_object;
}

classD.cpp

#include <classD.h>
#include <classA.h>

classD::classD(int argc, char** argv) : classB_object(argc, argv)
{
    classA_object = new classA(classB_object.classC_object);
}

1 Ответ

1 голос
/ 26 октября 2019

Ваш код не соответствует вашему описанию (например, 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
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...