optimizing binary arithmetic operations using move semantics - c++

I'm experimenting with the rvalue references with a simple Vector class, trying to eliminate unneeded temporaries in binary operations. After a little bit of struggle, I found that with the following two overloads for operator+():
// overload called if right = lvalue and left = lvalue/rvalue
friend Vector<T> operator+(Vector<T> a, const Vector<T>& b) {
a += b;
return a;
}
// overload called if right = rvalue and left = lvalue/rvalue
friend Vector<T> operator+(const Vector<T>& a, Vector<T>&& b) {
b += a;
return std::move(b);
}
I can ensure that in an expression like auto x = a+b+c+d+...;, as long as at least a or b is a temporary, the move constructor will be called without creating any new temporaries.
On the other hand even if one of the values after a and b (say d) is lvalue, that should technically be enough to avoid the copy. Is there any optimization that can compiler can do by scanning a given expression to find at least one temporary and start calling the operator+ based on that value?
Example 1:
#include "vector.h"
Vector<double> get() {
return {0, 1, 2, 6};
}
int main (){
auto a = get();
auto b = get();
auto c = a + get() + b;
std::cout << c << std::endl;
return 0;
}
Output:
calling move ctor
calling move ctor
[0, 3, 6, 18]
Example 2:
#include "vector.h"
Vector<double> get() {
return {0, 1, 2, 6};
}
int main (){
auto a = get();
auto b = get();
auto c = a + b + get();
std::cout << c << std::endl;
return 0;
}
Output:
calling copy ctor
calling move ctor
[0, 3, 6, 18]

I guess the answer is negative. In your code
auto c = a + b + get();
The compiler must first call operator+() on a and b. This execution order is predefined in laguage specification I think. The compiler should not execute b + get() first, because different execution order can result in different return value for (a + b + get()), along with different side effects. So the order of execution should remain unchaged.
And is there something compiler can do with a + b part in the first place? As both a and b are l-value, compiler must choose a function which takes two l-value argumenets. It's not that compiler developers cannot add new optimizations, but that compiler should only do as specified in language. Suppose that compiler uses r-value version of your function for a + b. And then, in your function, a or b will be modified (as they are r-value), which is something not intended: why do we need to modify operands to get the sum of them?
And if you are willing to make a and b modifiable in your function, just can cast them as r-value before calling your function, like std::move(a) + std::move(b) + get()
Hope this helps.

Related

Overloading operator+ multiple times in same statement

I have the following code to overload operator+, which works fine when the program gets executed.
In the main() function, when I re-write the statement to call the overloaded operator+ from res= t + t1 + t2, which works fine, to res = t + (t1 + t2), it does not work anymore. Can anyone provide me a solution, along with the reason?
A solution already found is to update the signatures of the operator+ from Test operator +(Test &a) to Test operator +(const Test &a). Here, I have used the const keyword in the parameter list.
#include <iostream>
using namespace std;
class Test
{
private:
int num;
public:
Test(int v)
{
num = v;
}
Test operator+(Test &a)
{
Test r(0);
r = num + a.num;
return r;
}
void show()
{
cout << "\n num = " << num;
}
};
int main()
{
Test t(10);
Test t1(20);
Test t2(60);
Test res(0);
res = t + t1 + t2;
res.show();
return 0;
}
The problem is that you are accepting an object by non-const reference, not by const reference.
The Test object returned by operator+() is a temporary. A non-const reference can't bind to a temporary.
The reason this was probably working before is the operator+ was executed from left to right - which would look something like this:
object + object + object
temporary + object
The temporary still has the function operator+(), so it can still be called.
On the other hand, when you use parenthesis, it executes like this:
object + object + object
object + temporary
That means that the temporary ends up in a, which, again, can't happen for the reasons stated above.
To fix, either a) turn it into a const reference, or b) pass by value (not recommended because it creates extra copies in memory you don't need):
// a
Test operator +(const Test &a)
// b
Test operator +(Test a)
I also highly recommend making this function a const as well:
// a
Test operator +(const Test &a) const
// b
Test operator +(Test a) const
Now you can add const objects even if they are on the right hand side as well.
(t1 + t2) evaluates to an rvalue expression.
But your operator+(&) can only bind to non-const lvalues. On the other hand const & can bind to both lvalues and rvalues, thus operator+(const &) works.

Why do you need to use const in following operator overloading?

This is a code for adding 3 objects of class Integer
#include <iostream>
using namespace std;
class Integer
{
int value;
public:
Integer(int i)
{
value = i;
};
int getValue()
{
return value;
}
friend Integer operator+(const Integer & a,const Integer & b)
{
Integer i(a.value+b.value);
return i;
}
};
int main()
{
Integer a(1), b(2), c(3);
Integer d = a + b + c; //****HERE****
cout << d.getValue() << endl;
return 0;
}
Can someone please explain why const is added to parameters in this case. If I remove const, during compilation I get invalid operands to binary operation error.
Adding to this question:
When I only add a and b without const, output is 3 without any errors. However, adding 3 a+b+c objects, I get an error. Why is that?
Let's say hypothetically we had written this function:
friend Integer operator+(Integer& a, Integer& b);
Stylistically, this isn't great because operator+ shouldn't be modifying its arguments. But let's say we don't care about that. Now, what happens when we try to do this:
Integer d = a + b + c;
This is grouped as (a+b)+c. The a+b subexpression will find our operator+(), which is a viable function because a and b are non-const lvalues of type Integer, and the result of that expression is an rvalue of type Integer.
The subsequent expression is {some rvalue Integer} + c. That expression we won't be able to find a viable function for. While c can bind to Integer&, the first argument cannot - you can't bind an rvalue to a non-const lvalue reference. So you'd have to write something like:
Integer tmp = a + b; // OK
Integer d = tmp + c; // also OK
but now we're introducing this extra name into our scope and an extra statement into our code just to get around the unviability of our function signature.
Because you're trying to bind a Temporary to a [non-const-]reference, which isn't allowed in C++.
In the code Integer d = a + b + c;, a + b is evaluated first, and creates a new temporary object which then gets evaluated against c. So the code then becomes Integer d = t<a+b> + c;. Because the first object is now a temporary, only three function signatures will accept it as a parameter: const Integer &, Integer, and Integer &&. The second is not advised because it will create a copy of the object (though, sometimes that's what you want!), and the latter isn't advised because it will refuse to accept persistent objects. The first option, then, is best, because it will accept any Integer object regardless of whether it's expiring or not.
Recommended Reading: What are rvalues, lvalues, xvalues, glvalues, and prvalues? It's very verbose, and will probably require several passes before you "get" it, but it's extremely valuable knowledge for any C++ programmer.

Return a large std::vector from a function [duplicate]

I have a very basic question in C++.
How to avoid copy when returning an object ?
Here is an example :
std::vector<unsigned int> test(const unsigned int n)
{
std::vector<unsigned int> x;
for (unsigned int i = 0; i < n; ++i) {
x.push_back(i);
}
return x;
}
As I understand how C++ works, this function will create 2 vectors : the local one (x), and the copy of x which will be returned. Is there a way to avoid the copy ? (and I don't want to return a pointer to an object, but the object itself)
What would be the syntax of that function using "move semantics" (which was stated in the comments)?
There seems to be some confusion as to how the RVO (Return Value Optimization) works.
A simple example:
#include <iostream>
struct A {
int a;
int b;
int c;
int d;
};
A create(int i) {
A a = {i, i+1, i+2, i+3 };
std::cout << &a << "\n";
return a;
}
int main(int argc, char*[]) {
A a = create(argc);
std::cout << &a << "\n";
}
And its output at ideone:
0xbf928684
0xbf928684
Surprising ?
Actually, that is the effect of RVO: the object to be returned is constructed directly in place in the caller.
How ?
Traditionally, the caller (main here) will reserve some space on the stack for the return value: the return slot; the callee (create here) is passed (somehow) the address of the return slot to copy its return value into. The callee then allocate its own space for the local variable in which it builds the result, like for any other local variable, and then copies it into the return slot upon the return statement.
RVO is triggered when the compiler deduces from the code that the variable can be constructed directly into the return slot with equivalent semantics (the as-if rule).
Note that this is such a common optimization that it is explicitly white-listed by the Standard and the compiler does not have to worry about possible side-effects of the copy (or move) constructor.
When ?
The compiler is most likely to use simple rules, such as:
// 1. works
A unnamed() { return {1, 2, 3, 4}; }
// 2. works
A unique_named() {
A a = {1, 2, 3, 4};
return a;
}
// 3. works
A mixed_unnamed_named(bool b) {
if (b) { return {1, 2, 3, 4}; }
A a = {1, 2, 3, 4};
return a;
}
// 4. does not work
A mixed_named_unnamed(bool b) {
A a = {1, 2, 3, 4};
if (b) { return {4, 3, 2, 1}; }
return a;
}
In the latter case (4), the optimization cannot be applied when A is returned because the compiler cannot build a in the return slot, as it may need it for something else (depending on the boolean condition b).
A simple rule of thumb is thus that:
RVO should be applied if no other candidate for the return slot has been declared prior to 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
Named Return Value Optimization will do the job for you since the compiler tries to eliminate redundant Copy constructor and Destructor calls while using it.
std::vector<unsigned int> test(const unsigned int n){
std::vector<unsigned int> x;
return x;
}
...
std::vector<unsigned int> y;
y = test(10);
with return value optimization:
y is created
x is created
x is assigned into y
x is destructed
(in case you want to try it yourself for deeper understanding, look at this example of mine)
or even better, just like Matthieu M. pointed out, if you call test within the same line where y is declared, you can also avoid construction of redundant object and redundant assignment as well (x will be constructed within memory where y will be stored):
std::vector<unsigned int> y = test(10);
check his answer for better understanding of that situation (you will also find out that this kind of optimization can not always be applied).
OR you could modify your code to pass the reference of vector to your function, which would be semantically more correct while avoiding copying:
void test(std::vector<unsigned int>& x){
// use x.size() instead of n
// do something with x...
}
...
std::vector<unsigned int> y;
test(y);
Compilers often can optimize away the extra copy for you (this is known as return value optimization). See https://isocpp.org/wiki/faq/ctors#return-by-value-optimization
The move constructor is guaranteed to be used if NRVO does not happen
Therefore, if you return an object with move constructor (such as std::vector) by value, it is guaranteed not to do a full vector copy, even if the compiler fails to do the optional NRVO optimization.
This is mentioned by two users who appear influential in the C++ specification itself:
Johathan Wakely at Is an object guaranteed to be moved when it is returned?
Howard Hinnant How to return an object from a function considering C++11 rvalues and move semantics?
Not satisfied by my Appeal to Celebrity?
OK. I can't fully understand the C++ standard, but I can understand the examples it has! ;-)
Quoting the C++17 n4659 standard draft 15.8.3 [class.copy.elision] "Copy/move elision"
3 In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
(3.1) — If the expression in a return statement (9.6.3) is a (possibly parenthesized) id-expression that names
an object with automatic storage duration declared in the body or parameter-declaration-clause of the
innermost enclosing function or lambda-expression, or
(3.2) — if the operand of a throw-expression (8.17) is the name of a non-volatile automatic object (other than
a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one),
overload resolution to select the constructor for the copy is first performed as if the object were designated
by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter
of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload
resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution
must be performed regardless of whether copy elision will occur. It determines the constructor to be called if
elision is not performed, and the selected constructor must be accessible even if the call is elided. — end
note ]
4 [ Example:
class Thing {
public:
Thing();
~ Thing();
Thing(Thing&&);
private:
Thing(const Thing&);
};
Thing f(bool b) {
Thing t;
if (b)
throw t; // OK: Thing(Thing&&) used (or elided) to throw t
return t; // OK: Thing(Thing&&) used (or elided) to return t
}
Thing t2 = f(false); // OK: no extra copy/move performed, t2 constructed by call to f
struct Weird {
Weird();
Weird(Weird&);
};
Weird g() {
Weird w;
return w; // OK: first overload resolution fails, second overload resolution selects Weird(Weird&)
}
— end example
I don't like the "might be used" wording, but I think the intent is to mean that if either "3.1" or "3.2" hold, then the rvalue return must happen.
This is pretty clear on the code comments for me.
Pass by reference + std::vector.resize(0) for multiple calls
If you are making multiple calls to test, I believe that this would be slightly more efficient as it saves a few malloc() calls + relocation copies when the vector doubles in size:
void test(const unsigned int n, std::vector<int>& x) {
x.resize(0);
x.reserve(n);
for (unsigned int i = 0; i < n; ++i) {
x.push_back(i);
}
}
std::vector<int> x;
test(10, x);
test(20, x);
test(10, x);
given that https://en.cppreference.com/w/cpp/container/vector/resize says:
Vector capacity is never reduced when resizing to smaller size because that would invalidate all iterators, rather than only the ones that would be invalidated by the equivalent sequence of pop_back() calls.
and I don't think compilers are able to optimize the return by value version to prevent the extra mallocs.
On the other hand, this:
makes the interface uglier
uses more memory than needed when you decrease the vector size
so there is a trade-off.
Referencing it would work.
Void(vector<> &x) {
}
First of all, you could declare your return type to be std::vector & in which case a reference will be returned instead of a copy.
You could also define a pointer, build a pointer inside your method body and then return that pointer (or a copy of that pointer to be correct).
Finally, many C++ compilers may do return value optimization (http://en.wikipedia.org/wiki/Return_value_optimization) eliminating the temporary object in some cases.

Temporary objects in C++

I was going through reference return and came across temporary objects. I don't understand how to identify them. Please explain using this example:
If a and b are objects of same class, consider binary operator+. If you use it in an expression such as f(a+b), then a+b becomes temporary object and f has to of form f(const <class name>&) or f(<class name>). It can't be of form f(<class name>&) However, (a+b).g() is perfectly alright where g() can even change contents of object returned by a+b.
When you say f(a + b), the parameter of f needs to bind to the value with which the function was called, and since that value is an rvalue (being the value of a function call with non-reference return type)*, the parameter type must be a const-lvalue-reference, an rvalue-reference or a non-reference.
By constrast, when you say (a + b).g(), the temporary object is used as the implicit instance argument in the member function call, which does not care about the value category. Mutable values bind to non-const and const member functions, and const values only bind to const member functions (and similarly for volatile).
Actually, C++11 did add a way to qualify the value category of the implicit instance argument, like so:
struct Foo()
{
Foo operator+(Foo const & lhs, Foo const & rhs);
void g() &; // #1, instance must be an lvalue
void g() &&; // #2, instance must be an rvalue
}
Foo a, b;
a.g(); // calls #1
b.g(); // calls #1
(a + b).g(); // calls #2
*) this is the case for an overloaded operator as in this example, and also for built-in binary operators. You can of course make overloaded operators which produce lvalues, though going against the common conventions would probably be considered very confusing.
Your confusion comes not from that you cannot identify temporary objects, in both cases result of a+b is temporary object, but wrong assumption that non const method requires lvalue and would not accept temporary object, which is not true.
For a simple case, think of following piece of code:
int func(int lhs, int rhs)
{
return lhs + rhs;
}
int main() {
int a = 1, b = 2, c = 3;
return func(a * c, b * c);
}
Because func takes two integers, the program must calculate the values of a * c and b * c and store them somewhere -- it can't store them in a or b or c. So the resulting code is equivalent to:
int lhsParam = a * c;
int rhsParam = b * c;
return func(lhsParam, rhsParam);
Again, at the end of func() we return a calculate value, lhs + rhs. The compiler must store it in a new place.
For integers and so forth this seems very simple, but consider instead
int function(std::string filename);
function("hello");
filename has to be a std::string, but you passed a const char*. So what the compiler does is:
std::string filenameParam = "hello"; // construct a new object
function(filenameParam);
just like the previous example, but this time it is hopefully clearer that we're constructing a temporary object.
Note: The convention of calling them "somethingParam" is just for clarity in this answer.

Relevance of const return type in NRVO cases

This is a follow up question from Calling constructor in return statement.
This a operator overload fun in a class.
const Integer operator+(const Integer& IntObject)
{
cout << "Data : " << this->data << endl;
return Integer(this->data + IntObject.data);
}
What is the relevance of const in the return type for such functions?
int main()
{
Integer A(1); //Create 2 object of class Integer
Integer B(2);
const Integer C = A + B; //This will work
Integer D = A + B; //This will also work
fun(A + B); //Will work
}
void fun(Integer F) {}
This is a case temporaries are not created during return step due to NRVO. The object to be returned is directly constructed on the callee's address.
Here's a better example:
struct Foo
{
void gizmo();
Foo const operator+(Foo const & rhs);
};
Now if you have a Foo x; Foo y;, then you cannot say:
(x + y).gizmo(); // error!
The constant return value means you cannot use it for non-constant operations. For primitive types this is not quite so relevant, because there aren't many non-constant operations you can perform on temporary objects, because lots of "interesting" operations (like prefix-++) aren't allowed on temporaries.
That said, with C++11 one should really try and adopt the new idiom of never returning constant values, since non-constant values are now amenable to move optimisations.
Some people used to suggest doing that, to prevent writing nonsense like A + B = C. However, in C++11 it can prevent some optimisations since it makes the return value unmovable. Therefore, you shouldn't do it.
In this case, it also prevents you from writing perfectly valid code like D = A + B + C, but that's just because the author forgot to declare the operator const.
There is no relevance in your code snippet, because you are making a copy of the returned value.
In general, it is difficult to find good reasons to return a const value. I can only see it having an effect in this type of expression, attempting to call a non-const method on a const temporary:
(someObject.someMethodReturningConstValue()).someNonConstMethod(); // error, calls non const method on const temporary
so you should only use it if you want to disallow calling non-const methods on temporaries. On the other hand, it kills move-semantics in C++11 so is discouraged.