I see a lot of code at work where people use emplace and emplace_back with a temporary object, like this:
struct A {
A::A(int, int);
};
vector<A> v;
vector<A>.emplace_back(A(1, 2));
I know that the whole point of emplace_back is to be able to pass the parameters directly, like this:
v.emplace_back(1, 2);
But unfortunately this is not clear to a few people. But let's not dwell on that....
My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?
For your reference... we're working with C++14.
My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?
It can't avoid a copy, in the general case. Since emplace_back accepts by forwarding references, it must create temporaries from a pure standardese perspective. Those references must bind to objects, after all.
Copy elision is a set of rules that allows a copy(or move) constructor to be avoided, and a copy elided, even if the constructor and corresponding destructor have side-effects. It applies in only specific circumstances. And passing arguments by reference is not one of those. So for non-trivial types, where the object copies can't be inlined by the as-if rule, the compiler's hands are bound if it aims to be standard conformant.
The easy answer is no; elision doesn't work with perfect forwarding. But this is c++ so the answer is actually yes.
It requires a touch of boilerplate:
struct A {
A(int, int){std::cout << "A(int,int)\n"; }
A(A&&){std::cout<<"A(A&&)\n";}
};
template<class F>
struct maker_t {
F f;
template<class T>
operator T()&&{ return f(); }
};
template<class F>
maker_t<std::decay_t<F>> maker( F&& f ) { return {std::forward<F>(f)}; }
vector<A> v;
v.emplace_back(maker([]{ return A(1,2); }));
live example.
Output is one call to A(int,int). No move occurs. In c++17 the making doesn't even require that a move constructor exist (but the vector does, as it thinks it may have to move the elements in an already allocated buffer). In c++14 the moves are simply elided.
is the compiler able to optimize this and skip the create and copy?
There is not necessarily a copy involved. If a move constructor is available, there will be a move. This cannot be optimized away, as the direct initialization case will just call the init constructor, while in the other case, the move constructor will be called additionally (including its side-effects).
Therefore, if possible, you should refactor that code.
Related
I'm working through Stroustrup's "Tour of C++ v2". It's certainly not a C++ beginner's book, but enjoyable.
I've had a google and look through SO but no joy on this one.
Now, I thought I understood when the compiler can utilise a move constructor, but clearly I don't. Here I show the move constructor and the function that I thought would use it. It doesn't. Only if I explicitly use std::move. Why is this? My understanding was that the local r would be "moved" implicitly on return.
template<typename T>
Vector<T>::Vector(Vector<T> && a) // move constructor
:elem{a.elem},sz{a.sz}{
a.elem=nullptr;
a.sz=0;
}
template<typename T>
Vector<T> moveVectorAfterAdd(const Vector<T> & v1, const Vector<T> & v2){
Vector<T> r = v1+v2;
return std::move(r);
//return r;
}
int main(void) {
Vector<double> v1(1);
Vector<double> v2=v1;
Vector<double> v3=v2;
Vector<double> v4=moveVectorAfterAdd(v1,v2);
return 0;
}
(As a side note, lldb won't let me even set a break point in the move constructor despite compiling with no optimizations if I don't actually use std::move.)
Any and all clarifications gladly received!
When do you need to explicitly call std::move and when not in cpp?
In short, and technically precise words: Use std::move when you have an lvalue that you want to be an rvalue. More practically: You would want to do that when there is a copy that you want instead to be a move. Hence the name std::move.
In the example, you return an automatic variable. There is no copy that can be avoided by using std::move because in the special case of returning an automatic variable, there will be a move even from an lvalue.
Here I show the move constructor and the function that I thought would use it. It doesn't.
Just because there is a move in the abstract machine, doesn't necessarily mean that there would be a call to the move constructor. This is a good thing because doing nothing can potentially be faster than calling the move constructor.
This is known as (Named) Return Value Optimization. Or more generally copy elision. Using std::move inhibits this optimization, so not only is it unnecessary in this case, but it is also counter productive.
The following code emulates some code I'm working with. Basically struct Foo allocates an std::vector member (d_vec) and then defines some other member to be a pointer to the vector's contents (d_buf).
#include <cstddef>
#include <vector>
struct Foo
{
Foo(std::size_t n)
: d_vec(n, 0.)
, d_buf(d_vec.data())
{}
std::vector<double> d_vec;
double* d_buf;
};
Now, the following looks fine to me:
void buildAndUseFoo()
{
Foo f{10};
// do stuff with f.d_buf, it is safe
// ...
}
What I am not sure about is this:
Foo buildAndReturnFoo()
{
Foo f{10};
return f;
}
void someMethod()
{
auto f = buildAndReturnFoo();
// is it safe to use f.d_buf?
// ...
}
I wonder if the d_vec's address could change from when it's created inside buildAndReturnFoo() to when it's used in someMethod(). Then if I attempted to dereference it, I would get undefined behavior.
Note: I have tested printing the addresses and they happened to be the same but I'd like to be sure this is guaranteed, and that I wasn't relying on "luck".
Note #2: I'm aware of safer approaches; I'm just looking to learn about this scenario.
Your struct is dangerous to copy or move in any kind of situation, not restricted to function returns.
When such a struct is copied/moved, the d_buf of the destination object still points to the original vector’s data. That’s almost certainly not what you intended. So you need to respect the spirit of the rule of 5[*] and implement a copy ctor, copy assignment operator, move ctor and move assignment operator that all do the right thing, i.e. update where d_buf points to. Or disable copy and/or move by deleteing those functions.
The alternative is to get rid of d_buf. Replace it with a member function buffer() that accesses the vector’s data() on the fly. Because getting that pointer is a cheap operation I’d lean towards this solution.
[*] The rule of 5 states that if you need to implement at least one of copy ctor, copy assignment, move ctor, move assignment or destructor, you need all five of them. Your struct doesn’t manage any resources explicitly, so you don’t need a destructor and technically it’s not the full Rule of 5. But its spirit still applies.
The observed behavior may be caused by:
C++11 move semantic, if vector is move constructed by transferring data to the new one
C++17 Elision of copy/move operations, aka RVO/NRVO
For the first reason, tranferring data without reallocating is very likely to happen, but is not enforced by the standard.
For the second reason, it is also very likely to happen, even before C++17, but it is named variable, so does not fall into
mandatory elision of copy/move operations.
So the observed behavior is very likely in practice, but is not guaranteed.
UPDATE: To be even more explicit, and avoid misunderstandings: What I am asking is, in case of returning a named value, does the C++17 standard GUARANTEE that the move constructor will be invoked if I do std::move on the return value?. I understand that if not using std::move, compilers are allowed, but not required, to entirely elide copying and move constructors and just construct the return value in the calling function directly. That is not what I want to do in my example, I want guarantees.
Consider
class A; // Class with heap-allocated memory and 'sane' move constructor/move assignment.
A a_factory(/* some args to construct an A object */)
{
// Code to process args to be able to build an A object.
A a(// args); // A named instance of A, so would require non-guaranteed NRVO.
return std::move(a);
}
void foo()
{
A result = a_factory();
}
In this scenario, does the C++ standard guarantee that no copying will take place when constructing the result object, i.e. do we have guaranteed move construction?
I do understand the drawbacks of explicit std::move on a return value, e.g. in cases where class A is unmovable, we cannot do late materialization of temporaries and get 0 copy even without a move constructor in the class. But my specific question is this - I come from a hard real-time background and the current status of NRVO not being guaranteed by the standard is less than ideal. I do know the 2 specific cases where C++17 made (non-named) RVO mandatory, but this is not my question.
emplace_back(...) was introduced with C++11 to prevent the creation of temporary objects. Now with C++17 pure lvalues are even purer so that they do not lead to the creation of temporaries anymore (see this question for more). Now I still do not fully understand the consequences of these changes, do we still need emplace_back(...) or can we just go back and use push_back(...) again?
Both push_back and emplace_back member functions create a new object of its value_type T at some place of the pre-allocated buffer. This is accomplished by the vector's allocator, which, by default, uses the placement new mechanism for this construction (placement new is basically just a way of constructing an object at a specified place in memory).
However:
emplace_back perfect-forwards its arguments to the constructor of T, thus the constructor that is the best match for these arguments is selected.
push_back(T&&) internally uses the move constructor (if it exists and does not throw) to initialize the new element. This call of move constructor cannot be elided and is always used.
Consider the following situation:
std::vector<std::string> v;
v.push_back(std::string("hello"));
The std::string's move constructor is always called here that follows the converting constructor which creates a string object from a string literal. In this case:
v.emplace_back("hello");
there is no move constructor called and the vector's element is initialized by std::string's converting constructor directly.
This does not necessarily mean the push_back is less efficient. Compiler optimizations might eliminate all the additional instructions and finally both cases might produce the exact same assembly code. Just it's not guaranteed.
By the way, if push_back passed arguments by value — void push_back(T param); — then this would be a case for the application of copy elision. Namely, in:
v.push_back(std::string("hello"));
the parameter param would be constructed by a move-constructor from the temporary. This move-construction would be a candidate for copy elision. However, this approach would not at all change anything about the mandatory move-construction for vector's element inside push_back body.
You may see here: std::vector::push_back that this method requires either CopyInsertable or MoveInsertable, also it takes either const T& value or T&& value, so I dont see how elision could be of use here.
The new rules of mandatory copy ellision are of use in the following example:
struct Data {
Data() {}
Data(const Data&) = delete;
Data(Data&&) = delete;
};
Data create() {
return Data{}; // error before c++17
}
void foo(Data) {}
int main()
{
Data pf = create();
foo(Data{}); // error before c++17
}
so, you have a class which does not support copy/move operations. Why, because maybe its too expensive. Above example is a kind of a factory method which always works. With new rules you dont need to worry if compiler will actually use elision - even if your class supports copy/move.
I dont see the new rules will make push_back faster. emplace_back is still more efficient but not because of the copy ellision but because of the fact it creates object in place with forwarding arguments to it.
Please pardon my lack of clarity on this topic. I am trying to create functions for inserting a big class into a vector. In this example, I use vector of ints as the big class.
#include <vector>
#include <iostream>
using namespace std;
vector<vector<int>> vectorOfVectors;
void fn(const vector<int> &v) {
vectorOfVectors.push_back(v);
}
void fn(vector<int> &&v) {
vectorOfVectors.push_back(std::move(v));
}
int main() {
vector<int> a({1});
const vector<int> b({2});
fn(std::move(a));
fn(b);
cout<<b[0];
}
Obviously, I want no copying to be done when possible. My questions:
Does this code do the right thing?
Is there a better way of doing this?
For the same approach to work with custom classes, do I need to define move constructors?
Does this code do the right thing?
Yes. C++11 added std::vector::push_back(T&&) for this very reason.
Is there a better way of doing this?
Your fn(const vector<int> &v) and fn(vector<int> &&v) are both doing the same thing, pushing the argument v onto the end of vectorOfVectors. In lieu of your two fn functions you could have used one function template that uses perfect forwarding.
template<typename T>
void fn(T &&v) {
vectorOfVectors.push_back(std::forward<T>(v));
}
How this works is thanks to the C++11 reference collapsing rules and std::forward. The template type T becomes vector<int>& in the case that v is an lvalue but vector<int>&& in the case that v is an rvalue. The reference collapsing rules means that vector<int>& && becomes vector<int>& while vector<int>&& && becomes vector<int>&&. This does exactly what you want, calling the version of push_back that does a copy in the case of an lvalue but the version that does a move in the case of an rvalue.
One downside is that this sometimes can lead to interesting diagnostics when you get things wrong. ("Interesting" here means hundreds of lines of inscrutable diagnostic text from either g++ or clang++). Another downside is that templates can result in a case of "converters gone wild".
For the same approach to work with custom classes, do I need to define move constructors?
Not necessarily. You'll get an implicitly declared move constructor if the class doesn't declare a user-defined destructor, copy constructor, copy assignment operator, or move assignment operator. The implicitly declared move constructor will be defined as deleted if the class has non-movable data members or derives from a class that can't be moved or deleted.
To me, that's a bit too much to remember. I don't know if this is a good practice or a bad one, but I've started using Foo(const Foo&)=default, with similar declarations for the other rule of five functions. I'll also qualify the constructors as explicit in many cases to avoid the "converters gone wild" problem.
It does avoid copying a.
Yes. Using push_back means you're forced to construct at least two objects while emplace_back and perfect forwarding could do less work.
template<typename... Ts>
auto fn(Ts &&...ts)
-> decltype(vectorOfVectors.emplace_back(std::forward<Ts>(ts)...), void())
{
vectorOfVectors.emplace_back(std::forward<Ts>(ts)...);
}
http://melpon.org/wandbox/permlink/sT65g3sDxHI0ZZhZ
3. As long as you're using push_back, you would need the classes to be move constructible in order to avoid copying. You don't necessarily need to define the move contructor yourself though, if you can get the default definition.