3

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?

14
  • In array z = x, it's actually the copy constructor which gets called, not the assignment operator. The copy constructor is what you should implement. Commented Oct 17, 2012 at 8:34
  • Also, you could use Python list semantics: z=x copies a reference, z(1,10)=x(1,10) copies a range, z() = x() copies everything. Commented Oct 17, 2012 at 8:35
  • Right, bad example. The assignment array z = x; is not eligible for copy elision, since z is not a temporary. I have updated the question accordingly. Commented Oct 17, 2012 at 9:09
  • again, the copy constructor will be called in array z = x(80,90). Commented Oct 17, 2012 at 9:15
  • Generally, yes. But it depends. See this answer. In any case, I'd like to implement the copy constructor and copy assignment operator in a way that they behave consistently, according the principle of least surprise. Commented Oct 17, 2012 at 9:19

1 Answer 1

1

Does it make more sense for assignment to always create a reference, and copying to be declared explicitly?

In practice, no. Expecting developers to remember that a = b will not copy is what got std::auto_ptr into hot water. The C++11 standards committee also decided on std::move and std::forward to allow developers to explicitly say "I'm not making copies."

What about: array z = x(80,90); 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.

This was your giveaway. You want "array" and "view to an array" to act differently, but you're representing them both with class array.

Implement a new class arrayview with a similar public interface and move the ranges a_ and b_ to arrayview.

array x, y; x = y; // "array = array" copies all elements x(30,40) = y(50,60); // "arrayview = arrayview" copies element range arrayview v = x(80,90); // "arrayview(const arrayview&) shallow copies x(30,40) = y; // NOT DEFINED, since this has no clear meaning array z = x(80,90); // NOT DEFINED, since this has no clear meaning 

Note that in your arrayview assignment operator, it should be valid to copy from an arrayview with the same resource, but in that specific case you should check whether std::copy_backward is needed instead of std::copy.

Sign up to request clarification or add additional context in comments.

1 Comment

You're right. Arrays and views need different semantics and thus different class names. Thanks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.