Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the "right" way to avoid Aliasing (e.g. when adding an element of a container to itself) in C++?

std::vector<int> a;
a.push_back(1);
a.push_back(a[0]);

I just learned that the code above can be very dangerous.

(If it's not obvious why, you're not alone... it wasn't obvious to me either.)

My questions:

  1. What is the "standard" way of dealing with it? Making a new variable and then assigning it immediately to something afterward seems a bit weird to me. Is there a better way of dealing with it?

  2. How do you train yourself to watch out for aliasing issues like this? What pattern(s) do you look for? I have no idea to recognize this situation; I only learned about aliasing when I learned about the restrict keyword in C, and only now do I understand what the issue really is.


Edit:

I'd love to accept an answer, but it doesn't seem like part (2) of the question has been answered. I'm wondering what strategies people use to locate aliasing mistakes in code they have written.

One strategy I've come up with so far is to avoid passing in the same value for in two parameters. (In this case, one parameter is implicit and one explicit.)

Are there any other easy things to notice and watch out for?

like image 514
user541686 Avatar asked Jun 02 '11 05:06

user541686


2 Answers

EDIT: Technically, the standard does not mandate that this is correct if the contained type has a no-throw copy constructor. I don't know any implementation where this does not hold anyway, as it would require producing two implementations of push_back when the generic one is just as efficient in all cases.

Aliasing is a problem in general, but not in this particular case. The code:

assert( v.size() > 0 );
v.push_back( v[0] );

Is guaranteed to be correct by the standard (C++03) through the exception guarantees (which are a really good reason not to implement your own containers, you will probably not get them right). In particular §23.1 [lib.container.requirements] / 10 dictattes:

Unless otherwise specified (see 23.2.1.3 and 23.2.4.3) [NOTE: both those references refer to insert on deque and vector respectively] all container types defined in this clause meet the following additional requirements:

— if an exception is thrown by a push_back() or push_front() function, that function has no effects.

Where the important bit is that if any exception is thrown in the operation, the container is left untouched, and that means that no iterator gets invalidated, which in turns means that the original region of memory is left untouched until it is guaranteed that no exceptions will be thrown (with the exception pun intended, of destructors). Because in general copy constructors can throw, the implementation must ensure that all copies are performed before destroying any object.

This becomes more evident in C++0x, when objects are not copied from one location to another, but rather moved. Because the copy of the new element might throw, it has to be performed before any of the moves are executed, or else you would be left in a situation where some of the objects in the original container have been invalidated.

like image 133
David Rodríguez - dribeas Avatar answered Oct 20 '22 08:10

David Rodríguez - dribeas


I guess this would be safe:

std::vector<int> a(1);
a.push_back(1);
a.push_back(int(a[0]));
like image 39
Ben Voigt Avatar answered Oct 20 '22 07:10

Ben Voigt