简体   繁体   中英

std::shared_ptr<Parent> to std::shared_ptr<Child>

I have an "interface" as .h file that has a virtual method like this:

class ISomeInterface {
    public:
        virtual std::shared_ptr<Parent> getX() = 0;
}

now that parent is "abstract" and in the implementors of the interface I use an actual class. So I wanted to do this:

class Implementor : public ISomeInterface {
    public:
        std::shared_ptr<Child> getX() = { return this->x; }
}

But then I get:

Could not convert '((Implementor*)this)->Implementor::parent' from 'std::shared_ptr' to 'std::shared_ptr'

So, basically the std::shared_ptr is a wrapper and the compiler does not know how to come from wrapper<apple> to wrapper<fruit> , even though apple extends fruit.

How can I circumvent that behaviour?

Edit: Looks like this is still not possible in c++, as covariant types are only working for pointers/references, not inside wrappers like std::shared_ptr... a shame :(

You can't. Covariant return types only work for raw pointers and references because the compiler knows how they work. For this to work for arbitrary types the compiler would need to be able to be told "this is safe to use with covariant return types", like C# does with out T generic parameters, but there's no such feature in C++.

Unfortunately, you need to return std::shared_ptr<Parent> in Implementor .

You could satisfy both the virtual interface and provide additional information to those who have access to the derived class by having two member functions:

struct Implementor : ISomeInterface
{
    shared_ptr<Parent> getX() override { return getX_fromImplementor(); }

    shared_ptr<Child> getX_fromImplementor()  // not virtual!
    {
        // real implementation here
    }
};

This is possible.

Now, naively, this isn't allowed in C++. Covariant return types in C++ don't work this way.

But this is C++. C++ doesn't stop and give up just because the language doesn't support a feature. We can simply write the feature ourselves.

The trick is you don't actually care if getX is actually virtual; you just want it to have virtual behavior.

We create a series of non-virtual getX with a series of getX_impl virtual functions it dispatches to. We use final and pointer based dispatching to make it resistant against small typos.

From the user side, it acts like covariant return types. Implementation wise, you have to write two short bits of boilerplate.

An important part of this design is that it does no possibly unsafe casts . An easy shortcut is to do away with the final and new getX_impl methods; the cost is that we cannot guarantee that a further-derived child will actually put a shared_ptr<Child> .

The final parent dispatches to the public interface. The public interface dispatches to a new virtual function that retunrs shared_ptr<Child> . Children who want to change behavior must override the one that returns shared_ptr<Child> ; they have no choice, the compiler enforces it.

class ISomeInterface {
  virtual std::shared_ptr<Parent> getX_impl(ISomeInterface *) = 0;
public:
  std::shared_ptr<Parent> getX() { return getX_impl(this); }
};

class Implementor : public ISomeInterface {
  std::shared_ptr<Child> x = std::make_shared<Child>();

  virtual std::shared_ptr<Parent> getX_impl(ISomeInterface*) final override { return getX(); }
  virtual std::shared_ptr<Child> getX_impl(Implementor*) { return this->x; }
public:
  std::shared_ptr<Child> getX() { return getX_impl(this); }
};

users just call getX() . It acts almost exactly like a virtual method with a covariant shared pointer return type. It even works with member function pointers!

Every class you implement with a new type finalizes the parent method, creates a new private virtual getX_impl method returning the new type, has the new parent call getX() , and exposes a public getX() { return getX_impl(this); } getX() { return getX_impl(this); } which dispatches to the correct overload.

Live example .

You could probably marginally simplify this with the CRTP, but... it would be harder to understand.

template<class D, class Child, class B>
struct getX_CRTP : B {

  std::shared_ptr<Child> getX() { return static_cast<D*>(this)->getX_impl(static_cast<D*>(this)); }
private:
  virtual decltype( std::declval<B&>().getX() ) getX_impl( B* ) final override { return getX(); }
  virtual std::shared_ptr<Child> getX_impl( D* ) = 0;
};

Now Implementor looks like:

class Implementor : public getX_CRTP<Implementor, Cihld, ISomeInterface>
{
  std::shared_ptr<Child> x = std::make_shared<Child>();
  virtual std::shared_ptr<Child> getX_impl(Implementor*) override { return this->x; }
};

which is shorter per derived class, but not really clearer.

We could probably also replace the pointer-to-own-type with a tag of some kind, reducing the bytes pointlessly pushed on the stack from sizeof(ptr) to 1 , and that left unintialized. Let me know if you spot it in your profiled build. ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM