Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any undefined behavior issues when moving data into a function and then back out to where it came from?

Tags:

c++

c++11

move

Consider the following function:

std::vector<int> pushIt(std::vector<int> v){
    v.push_back(10);
    return v; 
}

int main(){
    std::vector<int> vec;
    vec = pushIt(std::move(vec));
}

my assumption is that the vector is moved into the function, modified and moved back to its original spot. This should result in simmilar behavior as passing it in as a non const reference. This seems to be quite valid behavior but a colleague fears undefined behavior. Is there anything I am missing here?

I would like to do this because the current function

void currentPushIt(std::vector<int>& v){
    v.push_back(10);
}

has led to a lot of problems in code review because people overlook the fact that an innocent call to currentPushIt(v) can invalidate iterators. Making them write v=pushIt(std::move(v)) should wake them up enough that they don't make the same mistakes.

like image 863
odinthenerd Avatar asked Oct 20 '22 12:10

odinthenerd


2 Answers

Per 1.9p15, the value computations and side effects associated with the argument are sequenced before the execution of the body of the function. So already by the time you enter pushIt the source vec has been moved from. The assignment is then sequenced after the execution of pushIt, since you are actually calling the user-defined operator vector::operator=:

vec.operator=(       // sequenced after
    pushIt(          // the evaluation of this, which is sequenced after
        std::move(   // the evaluation of this
            vec)))

So your code is fine.

like image 96
ecatmur Avatar answered Oct 29 '22 21:10

ecatmur


Your colleagues fear is without reason, the snippet shown does not suffer from undefined behavior.


The snippet written, since std::vector is a class, is equivalent of the below, and since a function parameter must have a value before the function is called it's equivalent of first calling std::move, then pushIt, and later vec.operator=.

When viewed in this way it's quite clear that it's actually safe to write such code as you have done.

vec.operator= (pushIt (std::move (vec));

In general

a = do_something (a);

I've met developers who get worried about such snippets because = isn't a sequence-point; what if do_something modifies a, what happens to the left-hand side?

long story short; It doesn't matter. Even though we don't know in which order the left-hand side and the right-hand side will be evaluated it is well-defined in terms of correctness.


The left-hand side of = is an lvalue, which can be seen as a location of where a certain value will end up. No matter the value stored at this location, the actual location will always be the same.

The right-hand side will, in this case, move from vec. This will indoubtely change the value of vec, but it won't change the location of where vec is; so the result of the right-hand side will correctly be assigned to where it's supposed to.

Note: The actual assignment is sequenced, both lhs and rhs must be evaluated before this takes place (ie. before the value of rhs is assigned to the location yield by lhs), but the order of which lhs and rhs is evaluated is not set in stone.

like image 37
Filip Roséen - refp Avatar answered Oct 29 '22 22:10

Filip Roséen - refp