How do we return a unique_pointer member from a me

2020-06-16 09:09发布

问题:

I have a base class with a pointer member. I would have to make an educated guess to determine whether it should be an unique_ptr or a shared_ptr. None of them seems to solve my particular use case.

class Base
{
public:
    Base(): pInt(std::unique_ptr<int>(new int(10))) {};
    virtual std::unique_ptr<int> get() = 0;
    //Base(): pInt(std::shared_ptr<int>(new int(10))) {}; // Alternate implementation
    //virtual std::shared_ptr<int> get() = 0; // Alternate implementation
private:
    std::unique_ptr<int> pInt;
    //std::shared_ptr<int> pInt; // Alternate implementation
};

The base class has been derived onto Derived1 and Derived2. The former returns the unique_ptr member pInt where the later returns a local unique_ptr object.

class Derived1: public Base
{
public:
    Derived1() {};
    virtual std::unique_ptr<int> get()
    {
        //return std::move(pInt);  Will compile but the ownership is lost
        return pInt;
    }
private:
    std::unique_ptr<int> pInt;
};
class Derived2: public Base
{
public:
    Derived2() {};
    virtual std::unique_ptr<int> get()
    {
        std::unique_ptr<int> pInt(new int());
        return pInt;
    }
private:
    std::unique_ptr<int> pInt;
};

Derived1's implementation of get would not implicitly transfer the ownership as the member pointer variable is not an eXpiring value, where as the Derived2's implementation can. This behaviour is well documented in the standard

see 12.8 §34 and §35:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...] This elision of copy/move operations, called copy elision, is permitted [...] in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type [...]

When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

Nevertheless, if I explicitly transfer the ownership via the std::move, the member pointer would be unusable in the future.

Alternatively, I would have to make the definition of the pointer as shared_ptr but that would be an extra overhead for the implementation of Derived2::get.

Note It should be considered that the occurrence of Derived2::get is more compared to Derived1::get so the design decision of using std:: shared_ptr can have a considerable relative impact.

回答1:

Your Derived1 case cannot be handled the way you want by unique_ptr. You want multiple smart pointers to the same resource. unique_ptr is simply not an option for that. There's no way around that.

You could stick with a unique_ptr member, but make your function return a raw pointer.

virtual int *get() = 0;

This is troublesome for your Derived2 class, because it is not clear whether the caller should free the pointed-to memory. I recommend you do not do this.

You could use a shared_ptr member, as you suggested, and make your function return that. This is fully functional in your Derived2 class, but as you point out, sub-optimal.

It is still the cleanest solution, though. For callers that only know they've got a Base, you need some way of informing them (either manually or through the returned type) what they should do when they're done with get()'s result, so you cannot return unique_ptr<int> anyway.

The only way a function returning unique_ptr<int> could be useful is if the caller already knows you've got a Derived2. But then, you can just add a new member:

virtual shared_ptr<int> get() {
  return get_unique();
}
virtual unique_ptr<int> get_unique() {
    std::unique_ptr<int> pInt(new int());
    return pInt;
}

I would only do that if profiling shows that the shared_ptr<int> get() member actually adds measurable overhead, though. There's a good chance that your shared_ptr<int> implementation is sufficient, performance-wise, and then readibility should probably be a reason for not adding a new member.



回答2:

The purpose of unique_ptr is to point to a resource from a unique place. If you need to point to this resource from multiple places, unique_ptr is no longer appropriate. You should use shared_ptr in that case.
If releasing ownership of the resource is appropriate for your logic, then just use:

unique_ptr<int> get()
{
   return move(pInt);// this will move the ownership of the resource away from the member field
}

... but from now on, the pInt is no longer valid.

If you are careful with resources (if you are not worried of dangling pointers), than just return raw pointer to the resource (but please don't prefer this to use of shared_ptr).

In case of using shared_ptr, be careful of cyclic dependency, use weak_ptr do counter it. Here is something about it: http://geekwentfreak-raviteja.rhcloud.com/blog/2014/07/06/c11-how-to-create-cyclic-dependencies-with-shared_ptr-and-how-to-avoid-them/?_sm_au_=irVM6PVF1TR4nGMW

Post edit: When you use unique_ptr as a member field, you are loosing copy constructor, because unique_ptr cannot be copied. FYI

But still, use of unique_ptr seems to me as extra overhead already, in which just use shared_ptr or int directly.