unique_ptr ownership semantics
Asked Answered
L

2

5

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.

Lashanda answered 9/6, 2014 at 23:3 Comment(4)
What's OtherObject? AFAIK it can't be a function in that context.Kimura
@Jefffrey I'm unsure what confusion remains -- I rewrote the post to refer more directly to my issue.Lashanda
What do you mean by "dependency" ?Barathea
@MattMcNabb "dependency" in the sense of dependency injection -- basically just some object that the class needs to useLashanda
T
6

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))
    {}
};
Thunderclap answered 9/6, 2014 at 23:6 Comment(4)
OtherObject is a base class according to OP.Thunderclap
OtherObject is meant to be some arbitrary type, not a unique_ptr.Lashanda
Using the terminology of my edited post, I was wanting to avoid modifying Foo to provide a getter (mostly because it would require a static_cast), but the other solution isn't too attractive either, so I might just go with the casting accessor.Lashanda
@alecb: I have added an other alternative which may fit better with your use case.Thunderclap
K
1

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)) {}
Kimura answered 9/6, 2014 at 23:32 Comment(1)
I believe unique_ptr specifically guarantees it's null'ed out though?Electrolyte

© 2022 - 2024 — McMap. All rights reserved.