Avoiding Copy of Objects with the "Return" Statement

Avoiding copy of objects with the return statement

This program can take advantage of named return value optimization (NRVO). See here: http://en.wikipedia.org/wiki/Copy_elision

In C++11 there are move constructors and assignment which are also cheap. You can read a tutorial here: http://thbecker.net/articles/rvalue_references/section_01.html

Does return statement copy values

Yes, in that case there will be a copy made. If you change the function declaration like this:

subline_t &subline(int x1, int x2, int id) {

then no copy will be made. However, in your specific case it would not be valid to return a reference to an object allocated on the stack. The problem is that the object would be destructed and invalidated before the caller had a chance to use it.

This is related to the common Return Value Optimization for C++ that can avoid doing an actual copy operation in the case you have described. The end result is (or should be) the same as if a copy were done, but you should be aware of the optimization. The presence of this optimization can, in some cases, change the observable behaviour of the program.

Prevent object copy on return

You need to return by value instead of reference. Using

Point getPoint()
{
return { 100, 120 };
}

Allows C++17's guaranteed copy elision to kick in which causes Point p = getPoint(); to act as if it was Point p{ 100, 120 };


Side note: Never, Never, Never, return a function local object by reference. That object will be destroyed at the end of the function leaving you with a dangling reference and using that is undefined behavior.

How to avoid using a return statement in a for-in loop?

As @quamrana, I think that the loop is implemented well, except that you store posts only for the last loop run. Here is the correction:

def followingPosts(request, user_username):
try:
user_profile = get_object_or_404(User, username=user_username)
following_users = user_profile.get_following()
posts = list()
for person in following_users:
posts += Post.objects.filter(creator=person)
except Profile.DoesNotExist:
return JsonResponse({"error": "No user found."}, status=404)

if request.method == "GET":
return JsonResponse([post.serialize() for post in posts], safe=False)

else:
return JsonResponse({
"error": "GET request required."
}, status=400)

Also, it is a good habit to keep a single line in the try-clause. Therefore, what about

def followingPosts(request, user_username):
try:
user_profile = get_object_or_404(User, username=user_username)
except Profile.DoesNotExist:
return JsonResponse({"error": "No user found."}, status=404)

if request.method == "GET":
following_users = user_profile.get_following()
posts = list()
for person in following_users:
posts += Post.objects.filter(creator=person)
return JsonResponse([post.serialize() for post in posts], safe=False)

else:
return JsonResponse({
"error": "GET request required."
}, status=400)

C++ Return temporary values and objects that cannot be copied

Go ahead and let the default copy constructors be public. And document that a view or mutable_view is "invalidated" when its tensor is changed or destroyed.

This parallels how the Standard Library deals with iterators, pointers, and references that have a lifetime which depends on another object.

Is it possible to avoid the copy constructor when passing newly created object to a function?

Pass by value implies copying. This copying can be elided but semantically pass by value is still copying. You can pass by reference as well to avoid this copy. Note that these are the only situations in which copy elision are permitted:

  • in a return statement
  • in a throw-expression
  • with a temporary that hasn't been bound to a reference
  • and another to do with exceptions

That's why in:

bar(f);
bar(Foo());

only the second involves copy elision. Pre-C++11, just use a reference or use Boost.

Initialization in return statements of functions that return by-value

You have to be careful with cppreference.com when diving into such nitty-gritty, as it's not an authoritative source.

So which object exactly is copy-initialized as described at cppreference.com for return statements?

In this case, none. That's what copy elision is: The copy that would normally happen is skipped. The cppreference (4) clause could be written as "when returning from a function that returns by value, and the copy is not elided", but that's kind of redundant. The standard: [stmt.return] is a lot clearer on the subject.

To convert from A&& to A A's move constructor is used, which is also why NRVO is disabled here. Are those the rules that apply in this case, and did I understand them correctly?

That's not quite right. NRVO only applies to names of non-volatile objects. However, in return std::move(local);, it's not local that is being returned, it's the A&& that is the result of the call to std::move(). This has no name, thus mandatory NRVO does not apply.

I think this should be an Lvalue to rvalue conversion:

The A&& returned by std::move() is decidedly not an Lvalue. It's an xvalue, and thus an rvalue already. There is no Lvalue to rvalue conversion happening here.

but A a2(no_nrvo()); is a direct initialization. So this also touches on the first case.

Not really. Whether a function has to perform copy-initialization of its result as part of a return statement is not impacted in any way by how that function is invoked. Similarly, how a function's return argument is used at the callsite is not impacted by the function's definition.

In both cases, an is direct-initialized by the result of the function. In practice, this means that the compiler will use the same memory location for the an object as for the return value of the function.

In A a1(nrvo());, thanks to NRVO, the memory location assigned to local is the same as the function's result value, which happens to be a1 already. Effectively, local and a1 were the same object all along.

In A a2(no_nrvo()), local has its own storage, and the result of the function, aka a2 is move-constructed from it. Effectively, local is moved into a2.

Will std::move() upon object construction in return statement help or prevent RVO?

// version 1
MyObject Widget::GetSomething() {
return MyObject();
}

In C++03 this requires MyObject by copyable. At runtime, no copy will be made using any "real" compiler with reasonable settings as the standard permits elision here.

In C++11 or 14 it requires the object be movable or copyable. Elision remains; no move or copy is done.

In C++17 there is no move or copy here to elide.

In every case, in practice, MyObject is directly constructed in the return value.

// version 2
MyObject Widget::GetSomething() {
return std::move(MyObject());
}

This is invalid in C++03.

In C++11 and beyond, MyObject is moved into the return value. The move must occur at runtime (barring as-if elimination).

// version 3
MyObject Widget::GetSomething() {
auto obj = MyObject();
return obj;
}

Identical to version 1, except C++17 behaves like C++11/14. In addition, the elision here is more fragile; seemingly innocuous changes could force the compiler to actually move obj.

Theoretically 2 moves are elided here in C++11/14/17 (and 2 copies in C++03). The first elision is safe, the second fragile.

// version 4
MyObject Widget::GetSomething() {
auto obj = MyObject();
return std::move(obj);
}

In practice this behaves just like version 2. An extra move (copy in C++03) occurs in constructing obj but it is elided, so nothing happens at runtime.

Elision permits the elimination of side effects of the copy/move; the objects lifetimes are merged into one object, and the move/copy is eliminated. The constructor still has to exist, it is just never called.

Answer

Both 1 and 3 will compile to identical runtime code. 3 is slightly more fragile.

Both 2 and 4 compile to identical runtime code. It should never be faster than 1/3, but if the move can be eliminated by the compiler proving not doing it is the same as-if doing it, it could compile to the same runtime code as 1/3. This is far from guaranteed, and extremely fragile.

So 1>=3>=2>=4 is the order of faster to slower in practice, where "more fragile" code that is otherwise the same speed is <=.

As an example of a case that could make 3 slower than 1, if you had an if statement:

// version 3 - modified
MyObject Widget::GetSomething() {
auto obj = MyObject();
if (err()) return MyObject("err");
return obj;
}

suddenly many compilers will be forced to move obj into the return value instead of eliding obj and the return value together.

Why is the copy constructor called when we return an object from a method by value

The copy constructor is called because you call by value not by reference. Therefore a new object must be instantiated from your current object since all members of the object should have the same value in the returned instance. Because otherwise you would be returning the object it self, which would be returning by reference. In this case modifying the reference object would change the original as well. This is generally not a behavior one wants when returning by value.



Related Topics



Leave a reply



Submit