Как обработать 'этот' указатель в конструкторе?

У меня есть объекты, которые создают другие дочерние объекты в их конструкторах, передавая 'это' так, ребенок может сохранить указатель назад на его родителя. Я использую повышение:: shared_ptr экстенсивно в моем программировании как более безопасная альтернатива станд.:: auto_ptr или необработанные указатели. Таким образом, у ребенка был бы код таким как shared_ptr, и повышение обеспечивает shared_from_this() метод, который родитель может дать ребенку.

Моя проблема - это shared_from_this() не может использоваться в конструкторе, который не является действительно преступлением, потому что 'это' не должно использоваться в конструкторе так или иначе, если Вы не знаете то, что Вы делаете и не возражаете против ограничений.

Руководство по стилю C++ Google указывает, что конструкторы должны просто установить членские переменные на свои начальные значения. Любая сложная инициализация должна войти в явный Init () метод. Это решает 'эту в конструкторе' проблему, а также немногих других также.

То, что беспокоит меня, - то, что люди, использующие Ваш код теперь, должны не забыть называть Init () каждым разом, когда они создают один из Ваших объектов. Единственным путем я могу думать для осуществления, это при наличии утверждения, что Init () уже назвали во главе каждой функции членства, но это утомительно для записи и громоздкий для выполнения.

Есть ли какие-либо идиомы там, которые решают эту проблему на каком-либо шаге по пути?

18
задан Kyle 24 March 2010 в 19:01
поделиться

6 ответов

Используйте фабричный метод для двухэтапного построения и инициализации вашего класса, а затем сделайте функцию ctor & Init () закрытой. Тогда нет возможности создать свой объект неправильно. Просто не забудьте оставить деструктор общедоступным и использовать умный указатель:

#include <memory>

class BigObject
{
public:
    static std::tr1::shared_ptr<BigObject> Create(int someParam)
    {
        std::tr1::shared_ptr<BigObject> ret(new BigObject(someParam));
        ret->Init();
        return ret;
    }

private:
    bool Init()
    {
        // do something to init
        return true;
    }

    BigObject(int para)
    {
    }

    BigObject() {}

};


int main()
{
    std::tr1::shared_ptr<BigObject> obj = BigObject::Create(42);
    return 0;
}

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

Если вы хотите, чтобы объект оставался в стеке, вы можете использовать вариант вышеупомянутого шаблона. Как написано, это создаст временный и использует копию ctor:

#include <memory>

class StackObject
{
public:
    StackObject(const StackObject& rhs)
        : n_(rhs.n_)
    {
    }

    static StackObject Create(int val)
    {
        StackObject ret(val);
        ret.Init();
        return ret;
    }
private:
    int n_;
    StackObject(int n = 0) : n_(n) {};
    bool Init() { return true; }
};

int main()
{
    StackObject sObj = StackObject::Create(42);
    return 0;
}
16
ответ дан 30 November 2019 в 07:49
поделиться

Рекомендации Google по программированию на C ++ неоднократно подвергались критике здесь и в других местах. И это правильно.

Я использую двухфазную инициализацию только в том случае, если она скрыта за классом-оболочкой. Если бы ручной вызов функций инициализации работал, мы бы все равно программировали на C, а его конструкторы никогда не были бы изобретены.

8
ответ дан 30 November 2019 в 07:49
поделиться

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

5
ответ дан 30 November 2019 в 07:49
поделиться

Вам действительно нужно использовать shared_ptr в этом случае? Может у ребенка просто указатель? В конце концов, это дочерний объект, поэтому он принадлежит родительскому объекту, поэтому разве у него не может быть обычный указатель на его родительский объект?

0
ответ дан 30 November 2019 в 07:49
поделиться

KeithB высказал действительно хорошую мысль, которую я хотел бы расширить (в смысле, не относящемся к вопросу, но не помещающемся в комментарии):

В конкретном случае связи объекта с его подобъектами время жизни гарантировано: родительский объект всегда переживет дочерний объект. В этом случае дочерний (член) объект не разделяет владение родительским (содержащим) объектом, и shared_ptr не должен использоваться. Его не следует использовать ни по семантическим причинам (нет общего владения вообще), ни по практическим причинам: вы можете создать всевозможные проблемы: утечки памяти и некорректные удаления.

Для упрощения обсуждения я буду использовать P для обозначения родительского объекта и C для обозначения дочернего или содержащегося объекта.

Если время жизни P внешне обрабатывается с помощью shared_ptr, то добавление еще одного shared_ptr в C для ссылки на P будет иметь эффект создания цикла. Если у вас есть цикл в памяти, управляемой подсчетом ссылок, то, скорее всего, у вас есть утечка памяти: когда последний внешний shared_ptr, ссылающийся на P, выходит из области видимости, указатель в C все еще жив, поэтому счетчик ссылок для P не достигает 0 и объект не освобождается, даже если он больше не доступен.

Если P обрабатывается другим указателем, то когда указатель будет удален, он вызовет деструктор P, что приведет к вызову деструктора C. Количество ссылок для P в shared_ptr, который есть у C, достигнет 0, и это вызовет двойное удаление.

Если P имеет автоматическую длительность хранения, то когда будет вызван его деструктор (объект выйдет из области видимости или будет вызван деструктор содержащего объекта), то shared_ptr вызовет удаление блока памяти, который не был новым.

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

Рассмотрите возможность использования либо сырого указателя (обработка права собственности на ресурс через указатель небезопасна, но здесь право собственности обрабатывается извне, так что это не проблема), либо даже ссылки (которая также говорит другим программистам, что вы доверяете ссылающемуся объекту P, что он переживет ссылающийся объект C)

.
3
ответ дан 30 November 2019 в 07:49
поделиться

Объект, требующий сложной конструкции, звучит как работа для фабрики.

Определите интерфейс или абстрактный класс, который не может быть построен, плюс свободную функцию, которая, возможно, с параметрами, возвращает указатель на интерфейс, но за кулисами позаботится о сложности.

Вы должны думать о дизайне с точки зрения того, что должен делать конечный пользователь вашего класса.

0
ответ дан 30 November 2019 в 07:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: