часто сталкиваюсь с кодом вроде
/*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.
Это может иметь катастрофические последствия, поскольку ваш родитель не создан во время набора ссылок. Следующий пример продемонстрирует это:
#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 ++ инициализирует новый объект, и пока я работаю с это то у меня все хорошо. К сожалению, иногда людям везет; в других случаях они создают ошибки таким образом. Вы должны знать свои инструменты и как их использовать 🙂
Чтобы ответить на ваш вопрос с моим личным мнением: я стараюсь избегать этой конкретной конструкции, но иногда мне приходилось ее использовать. Даже размышления над изменением дизайна класса не избежали бы этого. И поэтому я подал этот повод под заголовком: «Ну да, иногда мой дизайн просто не может быть смоделирован в чистом чистом прямом OO, зависимости между классами слишком тесны, а производительность слишком важна».