семантика владения unique_ptr

Возможно, я пытался быть слишком общим. (Исходный вопрос ниже) Конкретно, у меня есть некоторая зависимость Dep от класса Foo. У меня также есть класс MockDep, и я определяю класс TestFoo. Вот его конструктор, который я пытался написать:

TestFoo(unique_ptr<MockDep> dep) : Foo(std::move(dep)), mock_dep_(dep.get()) {}

И конструктор Foo выглядит так:

Foo(unique_ptr<Dep> dep) : dep_(dep) {}

mock_dep_ объявляется в TestFoo как MockDep* mock_dep_, а dep_ объявляется в Foo как unique_ptr<Dep> dep_. Как я могу заставить mock_dep_ содержать адрес dep_? (поскольку вышеприведенное не работает, поскольку std::move(dep) обнуляет dep.)


Исходное сообщение:

У меня есть объект типа Foo, который я должен передать другому объекту типа OtherObject, который утверждает, что владеет им, но как указатель на его базовый класс. Однако я хочу получить указатель на дочерний объект, который я могу использовать для ссылки на него. Я написал что-то вроде:

Foo(std::unique_ptr<Child> thing) :
    OtherObject(std::move(thing)), child_(thing.get()) {}

OtherObject(std::unique_ptr<Base> thing, ...) { ... }

Однако это, похоже, не работает, так как std::move(thing), кажется, обнуляет указатель, который позже возвращается из thing.get().

Я могу изменить параметр Foo, чтобы он имел тип Child* вместо unique_ptr<Child>, но я бы предпочел иметь возможность сделать последнее, поскольку он явно документирует семантику владения.

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

изменить: Foo и OtherObject предназначены для классов, конструкторы которых я определяю.


person Alec    schedule 09.06.2014    source источник
comment
Что такое OtherObject? Насколько я знаю, это не может быть функцией в этом контексте.   -  person Shoe    schedule 10.06.2014
comment
@Джеффри мой плохой, сообщение отредактировано   -  person Alec    schedule 10.06.2014
comment
@Jeffrey Джеффри, я не уверен, какая путаница осталась - я переписал пост, чтобы он более непосредственно относился к моей проблеме.   -  person Alec    schedule 10.06.2014
comment
Что вы подразумеваете под зависимостью?   -  person M.M    schedule 10.06.2014
comment
Зависимость @MattMcNabb в смысле внедрения зависимостей — в основном просто какой-то объект, который должен быть использовать   -  person Alec    schedule 10.06.2014


Ответы (2)


Вы можете использовать:

Foo(std::unique_ptr<Child> thing) :
    OtherObject(std::move(thing)),
    child_(OtherObject.getChildPtr()) /* one accessor to get the pointer. */
{}

Если базовый объект OtherObject не предоставляет метод доступа к указателю, вы можете делегировать конструктор другому конструктору, например:

class Foo: public OtherObject
{
public:
    Foo(std::unique_ptr<Child> thing) : Foo(thing, thing.get()) {}

private:
    Foo(std::unique_ptr<Child>& thing, Child* child) :
        OtherObject(std::move(thing)),
        child_(child)
    {}
private:
    Child* child_;
};

Третьим решением было бы изменить порядок между OtherObject и child_ (чтобы иметь child_ раньше), введя другое производное:

class ChildPtr
{
public:
    ChildPtr(Child* child) : child_(child) {}

    Child* child_;
};

class Foo: private ChildPtr, public OtherObject
{
public:
    Foo(std::unique_ptr<Child> thing) :
        ChildPtr(thing.get()),
        OtherObject(std::move(thing))
    {}
};
person Jarod42    schedule 09.06.2014
comment
OtherObject - это базовый класс в соответствии с OP. - person Jarod42; 10.06.2014
comment
OtherObject должен быть произвольным типом, а не unique_ptr. - person Alec; 10.06.2014
comment
Используя терминологию моего отредактированного сообщения, я хотел избежать изменения Foo для предоставления геттера (в основном потому, что для этого потребуется static_cast), но другое решение также не слишком привлекательно, поэтому я мог бы просто пойти с аксессором приведения. - person Alec; 10.06.2014
comment
@alecb: я добавил другую альтернативу, которая может лучше подойти для вашего варианта использования. - person Jarod42; 10.06.2014

Обычно то, что происходит, описывается в стандарте как:

§17.6.5.15.1 Состояние перемещенных из библиотечных типов [lib.types.movedfrom]

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

Стандарт на самом деле конкретно описывает поведение std::unique_ptr в:

§20.8.1.4 Шаблон класса unique_ptr [unique.ptr]

Кроме того, u может по запросу передать право собственности другому уникальному указателю u2. По завершении такого переноса выполняются следующие постусловия:

  • u2.p равно предварительному переносу u.p,
  • u.p равен nullptr, и
  • если состояние u.d до передачи сохранялось, то такое состояние было передано u2.d.

В частности, dep, после построения подобъекта Foo по адресу:

Foo(std::move(dep))

is a nullptr.

Более того, если бы dep все еще был допустимым указателем, Foo(std::move(dep)) скопировал бы dep, что не имеет смысла для семантики std::unique_ptr (поскольку он не копируется).

То, что вы хотите сделать, это позволить ссылку на указанный объект с должным учетом случая (например, может ли unique_ptr быть nullpt? и т. д..) в Foo:

class Foo {
public:
    Foo(unique_ptr<Dep> dep) : dep_(dep) {}
    const Dep& get_dep() const { return *dep_; }
    Dep& get_dep()             { return *dep_; }
private:
    std::unique_ptr<Dep> dep_;
};

а затем просто создайте объект TestFoo как:

TestFoo(unique_ptr<MockDep> dep) : Foo(std::move(dep)) {}
person Shoe    schedule 09.06.2014
comment
Я полагаю, что unique_ptr специально гарантирует, что он обнулен? - person user541686; 10.06.2014