Передает (в конструкторе) указатель на класс, в котором содержится плохой дизайн, и если да, то каково решение?
Часто сталкиваюсь с кодом вроде
/*initializer list of some class*/:m_member(some_param,/* --> */ *this)
Причина, по которой это сделано, заключается в том, что m_member может вызывать функции-члены из класса, который его содержит...
//code in class that is m_member instance of
m_parent->some_function();
Мне лично это не нравится, потому что я считаю это патетическим замыслом ("Дорогой ребенок, ты знаешь, что ты делаешь с инкапсуляцией в своем классе"), но я хотел бы знать, в общем ли это плохое поведение, и если да, то как избежать такого рода дизайн.
РЕДАКТИРОВАТЬ: пожалуйста, не сосредотачивайтесь на этом в списке инициализаторов, скажем, это в теле ctor.
4 ответа
Это может иметь катастрофические последствия, поскольку ваш родитель не создан во время набора ссылок. Следующий пример продемонстрирует это:
#include <iostream>
using namespace std;
struct TheParent;
struct TheChild
{
TheChild(TheParent& parent);
TheParent& myParent;
};
struct TheParent
{
TheParent()
: mychild(*this)
, value(1)
{
cout << "TheParent::TheParent() : " << value << endl;
}
TheChild mychild;
int value;
};
TheChild::TheChild(TheParent& parent)
: myParent(parent)
{
cout << "TheChild::TheChild() : " << myParent.value << endl;
};
int main()
{
TheParent parent;
return 0;
}
Производит следующий вывод, четко отмечая неопределенное состояние родительского объекта:
TheChild::TheChild() : 1606422622
TheParent::TheParent() : 1
Итог: не делай так. Вместо этого вам лучше использовать динамическое дочернее распределение, но даже здесь есть предостережения:
#include <iostream>
using namespace std;
struct TheParent;
struct TheChild
{
TheChild(TheParent& parent);
TheParent& myParent;
};
struct TheParent
{
TheParent()
: mychild(NULL)
, value(1)
{
mychild = new TheChild(*this);
cout << "TheParent::TheParent() : " << value << endl;
}
~TheParent()
{
delete mychild;
}
TheChild* mychild;
int value;
};
TheChild::TheChild(TheParent& parent)
: myParent(parent)
{
cout << "TheChild::TheChild() : " << myParent.value << endl;
};
int main()
{
TheParent parent;
return 0;
}
Это даст вам то, на что вы, вероятно, надеетесь:
TheChild::TheChild() : 1
TheParent::TheParent() : 1
Обратите внимание, однако, что даже это имеет проблемы, если TheParent
является промежуточным классом в цепочке наследования, и вы хотите получить доступ к потенциально переопределенным виртуальным реализациям функций в производных классах, которые еще не созданы.
Опять же, суть в том, что, если вы обнаружите, что делаете это, вы, возможно, захотите подумать, зачем вам это нужно в первую очередь.
Это плохо, потому что неясно, насколько завершен родительский класс на момент создания m_member.
Например:
class Parent
{
Parent()
: m_member(this), m_other(foo)
{ }
};
class Member
{
Member(Parent* parent)
{
std::cout << parent->m_other << std::endl; // What should this print?
}
};
Немного лучший подход, если нужен родительский указатель, - для Member иметь метод setParent, вызываемый в теле конструктора.
Как и подавляющее большинство практик программирования, нельзя сказать, что это вообще плохо (а если да, то вы плохой человек и вам должно быть стыдно). Я использую это иногда, но это редко; однако, это не та вещь, которую я бы старался целенаправленно избегать, меняя дизайн своего класса.
Обратите внимание, как я часто использовал "я" в вышеприведенном абзаце, это верный признак того, что это очень субъективный вопрос.
Я рассматриваю язык как инструмент для реализации решения данной проблемы. По своему дизайну C++ допускает явное использование this
и другие языки ОО не делают. Таким образом, я рассматриваю языковые функции как инструменты в своем наборе инструментов, и время от времени появляется возможность использовать тот или иной инструмент.
Тем не менее, и вот где появляется стиль и практика кодирования, я должен знать, что я делаю. Я должен знать, как использовать мои инструменты, и я должен знать последствия их использования. Существует определенный порядок, в котором C++ инициализирует новый объект, и пока я работаю с этим, у меня все хорошо. К сожалению, иногда людям везет; в других случаях они создают ошибки таким образом. Вы должны знать свои инструменты и как их использовать:-)
Чтобы ответить на ваш вопрос с моим личным мнением: я стараюсь избегать этой конкретной конструкции, но иногда мне приходилось ее использовать. Даже размышления над изменением дизайна класса не избежали бы этого. И поэтому я подал этот повод под заголовком: "Ну, ну, иногда мой дизайн просто не может быть смоделирован в чистой и чистой прямой ОО, зависимости между классами слишком тесные, а производительность слишком важна".