I'm not really familiar with how move works in C++, and I need some help to clarify my understanding. I'm want to overload the operator+, and I'm have a couple of questions about it.
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
return tmp += m;
}
My first question is how to make temporary objects movable. As shown in my function above, both arguments are not meant to be mutated, therefore I need to create a third object to do the operation upon.
How can I make my return value usable for move operation. Is the return value supposed to be a reference as ap_n&? Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
How does C++ decide when an object is rvalue, or decide that a move operation is suitable on an object, and how can I tell the program that an object is safe to use for move operation.
ap_n operator+(const ap_n&, const ap_n&); // defined as in first question
ap_n operator+(ap_n&& n, const ap_n& m) { return n += m; }
ap_n operator+(const ap_n& n, ap_n&& m) { return m += n; }
ap_n operator+(ap_n&& n, ap_n&& m) { return n += m; }
My second question is whether it is necessary to create variants of function that accept rvalue arguments. Right now I have 4 functions, as shown, to be able to accept normal objects and rvalue objects.
Is writing all the combinations possible like this necessary? If I remove all but the first function, would the program still be able to perform move operation correctly?
As a debugging tip, something that can help with getting these things right is to print a message in the move constructor
ap_n(ap_n&& o): x_(std::move(o.x_)) { std::cerr << "Move constructed\n"; }
plus similar messages in other constructors and the destructor. Then you get a clear chronology of when and how instances are created and destroyed.
How can I make my return value usable for move operation. Is the return value supposed to be a reference as ap_n&? Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
Return the result by value. (Don't return a reference to a local variable, since the local variable goes out of scope immediately, making the reference invalid.) You might find this short article useful: Tip of the Week #77: Temporaries, Moves, and Copies. For more depth, check out cppreference on copy elision.
how to make temporary objects movable
By defining move constructor/assignment to your type.
Is the return value supposed to be a reference as ap_n&?
Not for operator+ when you return new object.
operator += on the other hand returns reference to lhs, so returns ap_n&.
How can I make my return value usable for move operation. Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
From return statement,
there is an automatic move when returning directly a local variable.
so return tmp; is sufficient.
return std::move(tmp); prevents NRVO
return tmp += m; does a copy, as you don't return "directly" tmp.
You should do:
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
tmp += m;
return tmp; // NRVO, or automatic move
}
return std::move(tmp += m); would prevent NRVO, and do the move.
How does C++ decide when an object is rvalue,
Roughly,
variables are l-value as there have name.
function returning l-value reference (ap_n&) returns l-value.
function returning r-value reference (ap_n&&), or by value (ap_n) returns r-value.
or decide that a move operation is suitable on an object, and how can I tell the program that an object is safe to use for move operation.
Overload resolution select the best match between valid candidate.
So it requires function taking by value or by r-value reference (or forwarding reference).
My second question is
It seems not the second ;-)
whether it is necessary to create variants of function that accept rvalue arguments. Right now I have 4 functions, as shown, to be able to accept normal objects and rvalue objects.
Is writing all the combinations possible like this necessary?
Single function taking by const reference or by value can be enough in general case,
unless you want that optimization. so mostly for library writer, or critical code.
Notice that your overloads should be rewritten to effectively do move operation (to reuse input temporary parameter):
ap_n operator+(ap_n&& n, const ap_n& m)
{
n += m;
return std::move(n); // C++11; C++14, C++17
return n; // C++20
}
or
ap_n&& operator+(ap_n&& n, const ap_n& m)
{
return std::move(n += m);
}
If I remove all but the first function, would the program still be able to perform move operation correctly?
With
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n}; // copy
tmp += m;
return tmp; // NRVO, or automatic move
}
You have 1 copy, one NRVO/move for any kind of parameters.
With
ap_n&& operator+(ap_n&& n, const ap_n& m) {
return std::move(n += m);
}
you have no moves, but you should be careful about lifetime of references, as
auto ok = ap_n() + ap_n(); // 1 extra move
auto&& dangling = ap_n() + ap_n(); // No move, but no lifetime extension...
With
ap_n operator+(ap_n&& n, const ap_n& m) {
n += m;
return std::move(n); // C++11, C++14, C++17 // move
return n; // C++20 // automatic move
}
you have 1 move, no copies.
auto ok = ap_n() + ap_n(); // 1 extra move possibly elided pre-C++17, 0 extra moves since C++17
auto&& ok2 = ap_n() + ap_n(); // No moves, and lifetime extension...
So with extra overload, you might trade copy to move.
Taking notes from Jarod42's answer, the following is the revised code.
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
tmp += n;
return tmp; // Allows copy, NRVO, or move
}
ap_n operator+(ap_n&& n, const ap_n& m) {
n += m;
return std::move(n); // Allows copy, or move
}
ap_n operator+(const ap_n& n, ap_n&& m) {
m += n;
return std::move(m); // Allows copy, or move
}
The amount of functions is also reduced to 3, since one that takes both rvalue reference will automatically use the second function.
Please tell me if I'm still misunderstanding this.
Related
I am going over A Tour of C++ (Section 5.2 Copy and Move). Following the instructions in the book, I have built a container called Vector (it mimics std::vector). My goal is to efficiently implement the following (element-wise) summation:
Vector r = x + y + z;
According to the book, if I don't have move assignment and constructor, + operator will end up copying Vectors unnecessarily. So I implemented move assignment and constructor, but I think the compiler still doesn't use them when I run Vector r = x + y + z;. What am I missing? I appreciate any feedback. Below is my code. I expect to see an output Move assignment, but I get no output. (The summation part works, it's just the move business that I am not sure)
Code
// Vector.h
class Vector{
public:
explicit Vector(int);
Vector(std::initializer_list<double>);
// copy constructor
Vector(const Vector&);
// copy assignment
Vector& operator=(const Vector&);
// move constructor
Vector(Vector&&);
// move assignment
Vector& operator=(Vector&&);
~Vector(){delete[] elem;}
double& operator[](int) const;
int size() const;
void show();
friend std::ostream& operator<< (std::ostream& out, const Vector& vec);
private:
int sz;
double* elem;
};
Vector operator+(const Vector&,const Vector&);
// Vector.cpp
Vector::Vector(std::initializer_list<double> nums) {
sz = nums.size();
elem = new double[sz];
std::initializer_list<double>::iterator it;
int i = 0;
for (it=nums.begin(); it!=nums.end(); ++it){
elem[i] = *it;
++i;
}
}
Vector::Vector(Vector&& vec) {
sz = vec.sz;
vec.sz = 0;
elem = vec.elem;
vec.elem = nullptr;
std::cout<<"Move constructor"<<std::endl;
}
Vector& Vector::operator=(Vector&& vec) {
if (this == &vec){
return *this;
}
sz = vec.sz;
vec.sz = 0;
elem = vec.elem;
vec.elem = nullptr;
std::cout<<"Move assignment"<<std::endl;
return *this;
Vector operator+(const Vector& vec1, const Vector& vec2){
if (vec1.size() != vec2.size()){
throw std::length_error("Input vectors should be of the same size");
}
Vector result(vec1.size());
for (int i=0; i!=vec1.size(); ++i){
result[i] = vec1[i]+vec2[i];
}
return result;
}
}
// Main
int main() {
Vector x{1,1,1,1,1};
Vector y{2,2,2,2,2};
Vector z{3,3,3,3,3};
Vector r = x + y + z;
} // Here I expect the output: Move assignment, but I get no output.
There is a move elision takes place.
According to the C++ 17 Standard (12.8 Copying and moving class objects)
31 When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the destructor for the
object have side effects. In such cases, the implementation treats
the source and target of the omitted copy/move operation as simply two
different ways of referring to the same object, and the destruction of
that object occurs at the later of the times when the two objects
would have been destroyed without the optimization.122 This elision of
copy/move operations, called copy elision, is permitted in the
following circumstances (which may be combined to eliminate multiple
copies):
(31.3) — when a temporary class object that has not been bound to a
reference (12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move
So the move constructor is omitted. The second temporary object created by the expression x + y + z; is directly built as the obejct r.
Vector r = x + y + z;
Also take into account that there is move elision in the return statement of the operator
(31.1) — in a return statement in a function with a class return type,
when the expression is the name of a non-volatile automatic object
(other than a function or catch-clause parameter) with the same
cvunqualified type as the function return type, the copy/move
operation can be omitted by constructing the automatic object directly
into the function’s return value
So the temporary object created in the expression x + y (within the operator) will be moved (that is there will be elision relative to the return statement - preceding quote) in the expression ( x + y ) + z where it will be used by constant lvalue reference and the new temporary obejct created in this expression will be built as r.
In whole in this code snippet
Vector x{1,1,1,1,1};
Vector y{2,2,2,2,2};
Vector z{3,3,3,3,3};
Vector r = x + y + z;
due to the move elision there will be created 5 objects of the class Vector.
can we swap the elements of vector v without using any extra space and return v?
vector<int>func(const vector<int>&v)
{
//write your code here
}
The function returns the vector by value, which means it makes a copy of the input. Hence, you can make the copy explicitly, modify the copy and return it. E.g.:
vector<int> func(vector<int> const& v) {
vector<int> u(v);
// modify u.
return u;
}
One could also make the caller copy the argument vector<int> func(vector<int> u), but that disables return value optimization and hence 2 copies of the vector are made (one for function parameter another for return). See copy elision for full details:
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
To modify the vector in-place without copying, the function must take a reference to non-const vector:
vector<int>& func(vector<int>& v) {
// modify v.
return v;
}
This is probably pretty similar to other questions; I looked around a bit but I don't know what I'm talking about well enough to be sure.
I'm writing a function that "should" be in-place, but which is implemented by a BLAS call. The BLAS call is not in-place so I need to make a temporary. Thus:
void InPlace(ArrayClass& U, const TransformMatrix* M){
ArrayClass U_temp;
CallBLASdgemm(U, M, U_temp); //Now U_temp contains the correct output.
U = std::move(U_temp);
}
Is this a valid use of std::move, or am I somehow breaking "copy elision" (or is it bad for some other reason)?
Edit: the signature of CallBLASDgemm was requested; it is
CallBLASdgemm(const ArrayClass& U, const TransformMatrix* M,
ArrayClass& V);
No copies are performed with or without copy elision in this case so that's already out of the question. Because U_temp is an lvalue the compiler has to call the copy constructor if you'd do :
U = U_temp;
However, you know that U_temp won't be used anymore and thus moving away its values is completely safe and can possibly be faster (that is, if ArrayClass implements a move assignment constructor). Using std::move here is fine and even encouraged.
Yes, this is a valid use case. If you have a named temporary (lvalue) the only way to move it into U is to use std::move to cast it to a rvalue.
I think what you are worried about is when people do return std::move(object);. That is a pessimization as that copy of object into the return value can be elided in most cases.
This is valid. However, what I'd do is this:
ArrayClass myCallBLASdgemm(const ArrayClass& U, const TransformMatrix* M) {
ArrayClass tmp;
CallBLASdgemm(U, M, tmp);
return tmp; // elided move
}
void InPlace(ArrayClass& U, const TransformMatrix* M){
U = myCallBLASdgemm(U, M);
}
which runs the same code, but does it without a temporary being visible in the outer scope.
In fact, myCallBLASdgemm is so clean you can probably eliminate InPlace and just call myClassBLASdgemm where you need it.
Consider the paradigmatic max template function, std::max():
// From the STL
// TEMPLATE FUNCTION _Debug_lt
template<class _Ty1, class _Ty2> inline
bool _Debug_lt(const _Ty1& _Left, const _Ty2& _Right,
_Dbfile_t _File, _Dbline_t _Line)
{ // test if _Left < _Right and operator< is strict weak ordering
if (!(_Left < _Right))
return (false);
else if (_Right < _Left)
_DEBUG_ERROR2("invalid operator<", _File, _Line);
return (true);
}
// intermediate #defines/templates skipped
// TEMPLATE FUNCTION max
template<class _Ty> inline
const _Ty& (max)(const _Ty& _Left, const _Ty& _Right)
{ // return larger of _Left and _Right
return (_DEBUG_LT(_Left, _Right) ? _Right : _Left);
}
... Or, in simpler form:
template<typename T> inline
T const & max(T const & lhs, T const & rhs)
{
return lhs < rhs ? rhs : lhs;
}
I understand why the max template must return by reference (to avoid expensive copies and to avoid the requirement for a copy constructor).
However, doesn't this lead to the possibility of dangling references, as in the following code?
int main()
{
const int & max_ = ::max(3, 4);
int m = max_; // Is max_ a dangling reference?
}
In this case, the code builds and runs fine (VS 2010) and the value m is set to 4. However, it strikes me that max_ is a dangling reference since the hard-coded rvalues 3 and 4 were passed directly to max, and the storage allocated for these hard-coded rvalue constants can rightfully be de-allocated by the time the following line of code is reached.
I could envision analogous edge-cases for full-fledged objects, such as
class A
{
friend bool operator<(A const &, A const &)
{
return false;
}
};
int main()
{
const A & a_ = ::max(A(), A());
A a = a_; // Is a_ a dangling reference?
}
Am I correct that this usage of max - in which rvalues defined within the call argument list are passed as arguments - is an example of a potential "gotcha" with the use of max, unavoidable since max needs to be defined to return a reference in order to avoid expensive copies (and to avoid the requirement of a copy constructor)?
Might there be real-life circumstances in which it would be good programming practice to define a version of max that returns its result by value, rather than by reference, for the reason discussed in this question?
Yes, it's a dangling reference.
Yes, it's a gotcha.
A version that returns by value might be useful provided that you use it by default, and switch to by-reference in the cases where you need it. If you use the by-reference one by default and only switch to the by-value when you need it then you'll still fall foul of the gotcha, because realizing that you need it is the same thing as realizing you should have written const A a_= ::max(A(), A());.
Unfortunately the by-value one introduces a new gotcha:
A a, b, c;
mymax(a, b) = c; // assigns to temporary, not to a or b
(Actually, looking at this code I reckon if you call it max_by_val then you won't write this).
You could return by const value, and some people used to recommend that operator overloads should return by const value. But that introduces a performance gotcha in C++11:
A a, b, c;
c = constmymax(a, b); // copies from const rvalue instead of moving.
and even in C++03 it prevents the equivalent explicit optimization swap(c, constmymax(a,b)).
Note: as noted by sellibitze I am not up-to-date on rvalues references, therefore the methods I propose contain mistakes, read his anwser to understand which.
I was reading one of Linus' rant yesterday and there is (somewhere) a rant against operator overloading.
The complaint it seems is that if you have an object of type S then:
S a = b + c + d + e;
may involve a lot of temporaries.
In C++03, we have copy elision to prevent this:
S a = ((b + c) + d) + e;
I would hope that the last ... + e is optimized, but I wonder how many temporaries are created with user defined operator+.
Someone in the thread suggested the use of Expression Templates to deal with the issue.
Now, this thread dates back to 2007, but nowadays when we think elimination of temporaries, we think Move.
So I was thinking about the set of overload operators we should write not to eliminate temporaries, but to limit the cost of their construction (stealing resources).
S&& operator+(S&& lhs, S const& rhs) { return lhs += rhs; }
S&& operator+(S const& lhs, S&& rhs) { return rhs += lhs; } // *
S&& operator+(S&& lhs, S&& rhs) { return lhs += rhs; }
Does this set of operator seems sufficient ? Is this generalizable (in your opinion) ?
*: this implementation supposes commutativity, it doesn't work for the infamous string.
If you're thinking about a custom, move-enabled string class, the proper way to exploit every combination of argument value categories is:
S operator+(S const& lhs, S const& rhs);
S operator+(S && lhs, S const& rhs);
S operator+(S const& lhs, S && rhs);
S operator+(S && lhs, S && rhs);
The functions return a prvalue instead of an xvalue. Returning xvalues is usually a very dangerous thing – std::move and std::forward are the obvious exceptions. If you were to return an rvalue reference you'd break code like:
for (char c : my_string + other_string) {
//...
}
This loop behaves (according to 6.5.4/1 in N3092) as if the code is:
auto&& range = my_string + other_string;
This in turn results in a dangling reference. The temporary object's life-time is not extended because your operator+ doesn't return a prvalue. Returning the objects by value is perfectly fine. It'll create temporary objects but these objects are rvalues, so we can steal their resources to make it very effective.
Secondly, your code should also not compile for the same reason this won't compile:
int&& foo(int&& x) { return x; }
Inside the function's body x is an lvalue and you can't initialize the "return value" (in this case the rvalue reference) with an lvalue expression. So, you'd need an explicit cast.
Thirdly, you're missing an const&+const& overload. In case both of your arguments are lvalues, the compiler won't find a usable operator+ in your case.
If you don't want so many overloads, you could also write:
S operator+(S value, S const& x)
{
value += x;
return value;
}
I intentionally didn't write return value+=x; because this operator probably returns an lvalue reference which would have led to copy construction of the return value. With the two lines I wrote the return value will be move constructed from value.
S x = a + b + c + d;
At least this case is very efficient because there is no unnecessary copying involved even if the compiler isn't able to elide the copies – thanks to a move-enabled string class. Actually, with a class like std::string you can exploit its fast swap member function and make it effective in C++03 as well provided you have a reasonably smart compiler (like GCC):
S operator+(S value, S const& x) // pass-by-value to exploit copy elisions
{
S result;
result.swap(value);
result += x;
return result; // NRVO applicable
}
See David Abraham's article Want Speed? Pass by Value. But these simple operators won't be as effective given:
S x = a + (b + (c + d));
Here the left hand side of the operator is always an lvalue. Since operator+ takes its left hand side by value this leads to many copies. The four overloads from above deal perfectly with this example, too.
It's been a while since I read Linus' old rant. If he was complaining about unnecessary copies with respect to std::string, this complaint is no longer valid in C++0x, but it was hardly valid before. You can efficiently concatenate many strings in C++03:
S result = a;
result += b;
result += c;
result += d;
But in C++0x you can also use operator+ and std::move. This will be very efficient, too.
I actually looked at the Git source code and its string management (strbuf.h). It looks well thought through. Except for the detach/attach feature you get the same thing with a move-enabled std::string with the obvious advantage that the resource it automatically managed by the class itself as opposed to the user who needs to remember to call the right functions at the right times (strbuf_init, strbuf_release).