2

I've got a common abstract superclass Base, with a variety of Derived classes. For convenience, I'd like to be able to operate on the base class as a value, rather than via pointers. I can achieve this by creating a Wrapper using the technique described in this answer. The Wrapper stores a unique_ptr<Base> and forwards method calls to that pointer, and has appropriate constructors allowing me to do

Derived derived;
Wrapper wrap = derived;
wrap.callMethodOnDerived();

so the user can pass, return, and store Wrapper without thinking about the underlying pointers. Base remains entirely hidden.

However, this ends up creating copies when they don't need to be. I.e

void fun(const Wrapper&);

fun(derived);                  // makes copy
const Wrapper& view = derived; // makes copy

will call the Wrapper(const Derived&) converting constructor, then passes a reference to that. Instead, I'd like it to create a const Wrapper& which points to the original object, with no copy being made. How can I do this?

#include <memory>

using BigVal = int; // Could be some expensive-to-copy type
using namespace std;

struct Base{
    Base(BigVal x) : value(x){
    }

    unique_ptr<Base> clone() const{
        return make_unique<Base>(value);
    }

    BigVal getValue() const{
        return value;
    }
protected:
    BigVal value;
};

struct Derived : public Base{
    Derived(BigVal x) : Base(x){
    }
};

struct Wrapper{
    unique_ptr<Base> ptr_;

    BigVal getValue() const{
        return ptr_->getValue();
    }

    // Ignore missing copy/move assignment operators
    Wrapper(const Wrapper& other){ 
        ptr_ = other.ptr_->clone();
    }

    Wrapper(Wrapper&& other){ 
        ptr_ = move(other.ptr_);
    }

    Wrapper(const Base& other){
        ptr_ = other.clone();
    }  

    Wrapper(Base&& other){
        ptr_ = make_unique<Base>(std::move(other));
    } 
};

void constFun(const Wrapper& t){
    cout<<"Const fun "<<t.getValue()<<endl;
}

int main()
{
    Base b1(1);
    Base b2(2);
    Derived d1(3);

    Wrapper w = d1; // copies

    Wrapper w2 = move(w); // moves
    Wrapper w3 = move(b1); // moves

    // No copy, just taking a reference:
    const Wrapper& w4 = w2; 
    constFun(w2);

    // Can I avoid a copy here?
    const Wrapper& w5 = b2;
    constFun(b2); 
}

I've attempted to fiddle with converting constructors of the form

Base::operator const Wrapper& (){ 
  // return 'view' on this object
} 

But that results in ambiguity when doing Wrapper w = Derived();

Another option could be to make Wrapper never make copies unless you call clone, or lazily make copies when it changes. Yet another would be an independent WrapperView class which would be used in place of const Wrapper&. But is there a way to make copies happen normally during assignment, but not when creating a reference? If not, what are the pros/cons of the other possibilities?

divibisan
  • 11,659
  • 11
  • 40
  • 58
Joe
  • 123
  • 4
  • you can always pass by reference without any copy. what's the problem? – apple apple Jan 28 '18 at 02:46
  • @appleapple If a function takes a parameter of type `const Wrapper&`, I can pass a `Wrapper` without copy; but if I pass a `Derived` or `Base`, the implicit converting constructor is called to turn it into a `Wrapper` first. – Joe Jan 28 '18 at 02:50
  • 1
    A wrapper is a value semantics polymorphic `Base`. You are mad if you want it to be a sometimes value sometimes view based on some random set of heuristics. A `Wrapper const&` and a `WrapperView` are not the same thing, and wanting them to be the same is going to cause you lots of pain. – Yakk - Adam Nevraumont Jan 28 '18 at 03:43
  • @Yakk Sure, maybe that's the wrong approach here. I'm just trying to figure out if there's a sane and self-consistent way to make polymophic value types work. With my original approach there's no way to accept a polymorphic value with consistent copy behavior, which is what I'm looking for. – Joe Jan 28 '18 at 04:55
  • @Joe why not just use `Base&`? no need for `Wrapper` at all – apple apple Jan 28 '18 at 14:04
  • @joe Sure, but if you are working with polymorphic value types, stop using `Derived` *in the same code base*. `Derived` is an implementation detail. If you want a wrapper around an also exposed polymorphic vtable based type, you need to distinguish between views and value type wrappers, as they are fundamentally different. `WrapperDerived:WrapperBase`, use something like an `any` to store the value (in `WrapperBase`), `WrapperDerived` knows that it actually has a `Derived` (implementation detail). No client code ever creates a `Base` or `Derived` directly. – Yakk - Adam Nevraumont Jan 28 '18 at 16:25
  • @Yakk The user needs to be able to create and operate on the `Derived` types as well. Yeah, I suppose I could make a full parallel Wrapper hierarchy that would work here. One downside could be the amount of boilerplate: if somebody wants to add a `Derived` subclass, they need to write a `Wrapper` which forwards every function call and constructor to the underlying type. Any way to avoid that? Also... can you elaborate on the benefit of `any` here? Why not just store a pointer to `Base` in `WrapperBase` and downcast it to `Derived` in each `WrapperDerived`? – Joe Jan 28 '18 at 19:01
  • @joe the usual -- SBO etc. Why heap allocate if you don't need to? But sure, you could store a value ptr to Base. – Yakk - Adam Nevraumont Jan 28 '18 at 20:26

0 Answers0