Передает (в конструкторе) указатель на класс, в котором содержится плохой дизайн, и если да, то каково решение?

часто сталкиваюсь с кодом вроде

/*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.

5

Решение

Это может иметь катастрофические последствия, поскольку ваш родитель не создан во время набора ссылок. Следующий пример продемонстрирует это:

#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 является промежуточным классом в цепочке наследования, и вы хотите получить доступ к потенциально переопределенным виртуальным реализациям функций в производных классах, которые еще не созданы.

Опять же, суть в том, что если вы обнаружите, что делаете это, вы, возможно, захотите подумать, зачем вам это нужно в первую очередь.

2

Другие решения

Это плохо, потому что неясно, насколько завершен родительский класс на момент создания 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, вызываемый в теле конструктора.

2

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

Обратите внимание, как я часто использовал «я» в вышеприведенном абзаце, это верный признак того, что это очень субъективный вопрос.

1

Я рассматриваю язык как инструмент для реализации решения данной проблемы. По своему дизайну C ++ допускает явное использование this и другие языки ОО не делают. Таким образом, я рассматриваю языковые функции в качестве инструментов в своем наборе инструментов, и очень часто можно использовать тот или иной инструмент.

Тем не менее, и вот где приходит стиль и практика кодирования, я должен знать, что я делаю. Я должен знать, как использовать мои инструменты, и я должен знать последствия их использования. Существует определенный порядок, в котором C ++ инициализирует новый объект, и пока я работаю с это то у меня все хорошо. К сожалению, иногда людям везет; в других случаях они создают ошибки таким образом. Вы должны знать свои инструменты и как их использовать 🙂

Чтобы ответить на ваш вопрос с моим личным мнением: я стараюсь избегать этой конкретной конструкции, но иногда мне приходилось ее использовать. Даже размышления над изменением дизайна класса не избежали бы этого. И поэтому я подал этот повод под заголовком: «Ну да, иногда мой дизайн просто не может быть смоделирован в чистом чистом прямом OO, зависимости между классами слишком тесны, а производительность слишком важна».

0
По вопросам рекламы [email protected]