Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unique_ptr ownership semantics

Perhaps I was trying to be too generic. (Original question below) Concretely, I have some dependency Dep of a class Foo. I also have a class MockDep and am defining a class TestFoo. Here is its constructor I tried to write:

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

And Foo's constructor looks like:

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

mock_dep_ is delcared in TestFoo as MockDep* mock_dep_, and dep_ is declared in Foo as unique_ptr<Dep> dep_. How can I get mock_dep_ to contain dep_'s address? (as the above doesn't work since std::move(dep) nulls out dep.)


Original post:

I have an object of type Foo that I'm to pass to a different object of type OtherObject which claims ownership of it, but as a pointer to its base class. However, I want to grab a pointer to the child object that I can use to reference it. I wrote something like:

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

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

However, this doesn't seem to work, as the std::move(thing) seems to null out the pointer that returns from thing.get() later.

I can change Foo's parameter to be of type Child* instead of unique_ptr<Child>, but I'd prefer to be able to do the latter as it explicitly documents the ownership semantics.

What's the most appropriate (or failing that, unobtrusive) way of resolving this?

edit: Foo and OtherObject are both meant to be classes whose constructors I'm defining.

like image 214
alecbz Avatar asked Jun 09 '14 23:06

alecbz


2 Answers

You may use:

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

If base object OtherObject doesn't provide an accessor to the pointer, you may delegate the constructor to an other constructor, something like:

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_;
};

A third solution would be to change the order between OtherObject and child_ (to have child_ before) by introducing an other derivation:

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))
    {}
};
like image 197
Jarod42 Avatar answered Oct 20 '22 16:10

Jarod42


Generally what happens is described in the standard as:

§17.6.5.15.1 Moved-from state of library types [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

The standard actually specifically describe the behavior of std::unique_ptr in:

§20.8.1.4 Class template unique_ptr [unique.ptr]

Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of such a transfer, the following postconditions hold:

  • u2.p is equal to the pre-transfer u.p,
  • u.p is equal to nullptr, and
  • if the pre-transfer u.d maintained state, such state has been transferred to u2.d.

Specifically, dep, after the construction of the Foo sub-object at:

Foo(std::move(dep))

is a nullptr.

Moreover if dep was still a valid pointer, Foo(std::move(dep)) would have copied dep, which doesn't make sense for std::unique_ptr's semantic (as it is non-copyable).

What you want to do is let a reference to the pointed object, with the due considerations of the case (for example, can the unique_ptr be nullpt? etc..), in 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_;
};

and then simply construct the TestFoo object as:

TestFoo(unique_ptr<MockDep> dep) : Foo(std::move(dep)) {}
like image 44
Shoe Avatar answered Oct 20 '22 18:10

Shoe