Consider the following simplified example of a class holding a shared resource:
class array
{
typedef std::array<float, 1000> resource;
public:
// default constructor, creates resource
array() : p_(std::make_shared<resource>()), a_(0), b_(1000) {}
// create view
array operator()(int a, int b) { return array(p_, a_+a, a_+b); }
// get element
float& operator[](int i) { return (*p_)[a_+i]; }
private:
std::shared_ptr<resource> p_;
int a_, b_;
// constructor for views
array(std::shared_ptr<resource> p, int a, int b) : p_(p), a_(a), b_(b) {}
};
Now I'm wondering how to define a semantics for this class that doesn't confuse its users. For example, I'd like to allow operator=()
to copy elements:
array x, y;
x = y; // copies all elements from y's storage to x's
x(30,40) = y(50,60); // copies 10 elements
But then, to be consistent, shouldn't the copy constructor and the copy assignment operator always copy? What about:
array z = x(80,90); // will create an array referencing x's storage
In this case, the copy will be elided by the compiler, so no matter what my copy assignment operator does, z
will hold a reference to x
's storage. There's no way around this.
So does it make more sense for assignment to always create a reference, and copying to be declared explicitly? For example, I could define a wrapper class copy_wrapper
, assignment of which forces copying of elements:
class copy_wrapper
{
array& a_;
public:
explicit copy_wrapper(array& a) : a_(a) {}
array& get() { return a_; }
};
class array
{
// ...
array& operator=(const array& a); // references
array& operator=(copy_wrapper c); // copies
copy_wrapper copy() { return copy_wrapper(*this); }
};
This would force users to write:
array x, y;
x(30,40) = y(50,60).copy(); // ok, copies
x(30,40) = y(50,60); // should this throw a runtime error?
x = y; // surprise! x's resource is destructed.
A bit cumbersome, and worse than that: not what you expect.
How should I deal with this ambiguity?