4

I'm writing a class for Matrix arithmetic, and one feature I'm implementing is that you can "slice" a matrix and get another matrix back, but done such that the returned matrix references the parent's memory. This is very useful if you want to things like get a subsection of a matrix or add a vector to a column or things like that.

However, I wanted to implement it so that if the returned matrix is assigned or copied, the aliasing is broken and the memory is copied, so that you can't easily pass an aliased matrix around forever.

In playing with this, I have something like this:

matrix B = A.slice(1,1);  

A.slice(1,1) returns a sub-matrix of A (offset 1 row and 1 column). I implemented the = operator to break the aliasing, but to my chagrine, it isn't called when doing this, even with -O0 on. Similarly:

matrix B(A.slice(1,1)); 

Doesn't call the copy constructor (also written to break aliasing). The only thing that works is:

matrix B; B = A.slice(1,1);

My question is, obviously since I'm initializing B directly from A in the first two examples, it's taking some sort of shortcut, while I explicitly create B first in the last example and then assign A.slice(1,1) to it.

Can someone provide me with some insight into what the rules are in cases like these for when copy constructors and assignment operators are called?

gct
  • 14,100
  • 15
  • 68
  • 107
  • Have you tried stepping through the code with a debugger? If so, what is being called for the first two examples? I would've expected the copy constructor in both cases… – Ben Hocking Feb 12 '12 at 22:31
  • possible duplicate of [What is copy elision and how does it optimize the copy-and-swap idiom?](http://stackoverflow.com/questions/2143787/what-is-copy-elision-and-how-does-it-optimize-the-copy-and-swap-idiom) and [Conditions for copy elision?](http://stackoverflow.com/questions/6383639/conditions-for-copy-elision) – Ben Voigt Feb 12 '12 at 22:33
  • Also, are you returning a const reference to a `Matrix`? If not, I would recommend it. – Ben Hocking Feb 12 '12 at 22:33
  • @BenHocking: I wouldn't recommend returning a reference to a temporary object. – Ben Voigt Feb 12 '12 at 22:36
  • I tried stepping through the code, my Matrix::slice method uses a private constructor to directly build and returned the aliased matrix, and that's the only constructor that I see called. No copy constructor or assignment operators. – gct Feb 12 '12 at 22:38
  • @BenVoigt: Absolutely. I wan assuming it wasn't (literally) a temporary object. – Ben Hocking Feb 12 '12 at 22:38
  • The first isn't assignment. As you said, "I'm initializing B directly from A in the first two examples". Well, actually only the second is *direct-initialization*, the first is *copy-initialization*, but neither one is assignment. So the copy constructor would be called in both the first two cases, if it wasn't for copy elision. For the exact rules, see the two questions I linked above. – Ben Voigt Feb 12 '12 at 22:38
  • Thank Ben Voigt, I think that other question is directly related to what I'm seeing. Thinking about, I Want to be able to assign a slice into a variable once, and then have it broken on further copies, which is what happens. The copy elision optimization seems to explain it. – gct Feb 12 '12 at 22:40

1 Answers1

4

This is called copy elision, or return value optimization (§12.8.31 of the C++11 standard). The copy constructor can be skipped for virtually any function that returns a class type. This is often closely related to the actual implementation of returning class types. (Note that you're sort of relying on this too: presumably slice returns your matrix type by value, and you don't want a copy constructor called there if it would break your aliasing.)

You'll most likely need to implement this in some other way—for example, slice returning some kind of proxy type that supports the same operations and can convert to your normal matrix type, breaking aliasing during that conversion.

John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
  • DO you know if I can count on the copy elision behavior, or if it's required by the C++ standard (I'm not using C++11 here). You're right in that if it's not something I can count on then returning by value will copy and break the aliasing when there's no copy elision, but won't when there is... – gct Feb 12 '12 at 22:46
  • No, you cannot count on it, unless your compiler documents that it always happens. It's technically an optional optimization. But it's very likely to always be used for a one-step return, since it's related to the implementation (large enough classes tend to be returned by passing a pointer to storage and constructing it there), but two or more levels between the constructing function and the final use will almost certainly vary between compilers and optimization settings. – John Calsbeek Feb 12 '12 at 22:52
  • OK I'll have to think on it a bit how else to implement this then. – gct Feb 12 '12 at 22:58