Это плохая практика ссылаться на объект члена класса из другого члена того же класса? - PullRequest
0 голосов
/ 05 апреля 2020

Я довольно новичок в C ++ (с Java опытом) и пытаюсь понять принципы и методы проектирования классов.

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

Исходя из этого, вот мой дизайн (версия 1):

class Outer {
    Some_type _member;
    Inner _inner;
};

class Inner {
    Some_type* _ptr_to_outer_member;
};

Я знаю, что время жизни ссылочного объекта (Outer._member) необходимо гарантированно превышать время жизни ссылающегося объекта (Inner) и его указателя _ptr_to_outer_member, чтобы избежать висящего указателя. Здесь это условие должно быть удовлетворено тем фактом, что _inner является составной частью _outer, а Outer._member является членом объекта, поэтому он всегда инициализируется.

Теперь мне, конечно, не нужно Outer быть копируемым. Даже быть подвижным не обязательно, поскольку его нельзя перемещать после того, как все настроено, хотя было бы удобно иметь его подвижным при создании других объектов. Во всяком случае с этим дизайном, когда Outer перемещается _inner._ptr_to_outer_member становится недействительным. - Этого можно избежать, отключив операции копирования и перемещения для Outer, но кажется, что это просто обходная практика. Есть руководство, которое гласит, что следует избегать хранения ссылок на переменные, выделенные стеком, и в этом случае мне кажется, что у него могут быть аналогичные причины, которых следует избегать.

Альтернатива, которую я вижу, состоит в том, чтобы выделять кучу _member следующим образом ( версия 2):

class Outer {
    std::unique_ptr<Some_type> _member;
    Inner _inner;
};

class Inner {
    Some_type* _ptr_to_outer_member;
};

Outer теперь можно перемещать и не копировать. Это то, что я хочу, но за счет кучи, выделяющей _member, что противоречит рекомендациям о предпочтении членов объекта.

Таким образом, у меня возникает вопрос: есть ли какие-либо рекомендации по этому поводу? Я имею в виду что-то вроде использования указателя на объект в качестве членов, когда вы хотите поделиться объектом (как в версии дизайна 2) - это, вероятно, будет наиболее разумным для меня (если я что-то упустил). Или просто отключить перемещение для таких объектов (как в версии дизайна 1) и иметь дело с ним, поскольку оно неподвижно.

Или в примерах что-то принципиально не так?

Заранее спасибо для любого понимания этого.

1 Ответ

0 голосов
/ 05 апреля 2020

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

В остальном руководство: не усложняйте . Как она попала в эту иерархию с такой взаимозависимостью? Может ли быть лучший способ структурировать ваше приложение?

Возможны двунаправленные ссылки, но вам нужно поддерживать их самостоятельно или сделать Outer не копируемыми и не перемещаемыми:

class Outer {
    Some_type member;
    std::unique_ptr<Inner> inner;
public:
    Outer() {}

    Outer(Outer const& that) = delete; // makes Outer not copyable/movable
    Outer& operator=(Outer const& that) = delete;

    /* The only way to update inner, also sets the back-reference */
    void setInner(std::unique_ptr<Inner> ptr) noexcept {
        inner = std::move(ptr);
        if (inner) {
            inner->ptr_to_outer_member = &member;
        }
    }
};

Если Outer является подвижным, он должен активно поддерживать все обратные ссылки на себя, чтобы держать их в синхронизации c.

Например, вот так:

class Some_type {
};
class Inner {
    Some_type* ptr_to_outer_member;
    friend class Outer; // so that Outer may reach the private ptr_to_outer_member
};

class Outer {
    Some_type member;
    std::unique_ptr<Inner> inner; // owning pointer to Inner
public:
    Outer() noexcept {
    }
    Outer(Outer&& that) noexcept {
        *this = std::move(that);
    }
    Outer& operator=(Outer&& that) noexcept {
        member = std::move(that.member);
        setInner(std::move(that.inner));
        return *this;
    }
    /* The only way to update inner, also sets the back-reference */
    void setInner(std::unique_ptr<Inner> ptr) noexcept {
        inner = std::move(ptr);
        if (inner) {
            inner->ptr_to_outer_member = &member;
        }
    }
};

int main() {
    Outer o;
    o.setInner(std::make_unique<Inner>());
    Outer o2(std::move(o)); // OK, move-construction
    Outer o3;
    o3 = std::move(o2); // OK, move-assignment
    Outer o4(o3); // error: Outer is not copyable
}

Имея Обратные ссылки в подвижном типе почти всегда требуют пользовательского конструктора копирования / перемещения. Это означает, что мы также должны помнить правило 3/5/0 . В этом примере unique_ptr избавляет нас от наличия собственного деструктора. Это не всегда так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...