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

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

/*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++ инициализирует новый объект, и пока я работаю с этим, у меня все хорошо. К сожалению, иногда людям везет; в других случаях они создают ошибки таким образом. Вы должны знать свои инструменты и как их использовать:-)

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

Другие вопросы по тегам