Подтвердить что ты не робот

Как обращаться с указателем 'this' в конструкторе?

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

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

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

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

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

4b9b3361

Ответ 1

Используйте метод factory для 2-фазной конструкции и инициализируйте свой класс, а затем сделайте функцию 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;
}

EDIT:

Если вы хотите возразить, чтобы жить в стеке, вы можете использовать вариант вышеупомянутого шаблона. Как написано, это создаст временное и будет использовать копию 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;
}

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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