With this sample:
// test.cpp
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
class mystring : public string { public:
mystring() = default;
mystring(const char* c) : string(c) {}
mystring(mystring& m) : string(m) { cout << "Reference" << endl; }
mystring(mystring const & m) : string(m) { cout << "Const reference" << endl; }
mystring(mystring&& m) : string(move(m)) { cout << "Move" << endl; }
};
int main() {
mystring a;
vector<mystring> v{ a };
}
The ouput is:
$ g++ --version | grep "g++"
g++ (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
$ g++ -std=c++11 -fno-elide-constructors test.cpp
$ ./a.out
Reference
Move
Const reference
But, if I initialice v with a r-value:
vector<mystring> v{"hello"};
the output is:
$ g++ -std=c++11 -fno-elide-constructors test.cpp
$ ./a.out
Const reference
that means, no copies. With a r-value:
vector <mystring> v{mystring()};
output:
$ g++ -std=c++11 -fno-elide-constructors test.cpp
$ ./a.out
Move
Move
Const reference
I don’t understand two things:
- Why with no-raw strings a second move (before the first copy/move) is performed?
- Why with raw strings, no copies of “mystring” are performed?
N.B. raw strings means something different in C++11, I think you mean string literal if you’re talking about
"hello"Eh? There’s one copy, that’s why it prints
Const Reference.The braced-init-list initializes the
std::initializer_list<mystring>constructor parameter with a single element, which is constructed using themystring(const char* c)constructor, then that element is copied into the vector using themystring(const mystring&)constructor, which printsConst Reference. Pretty simple.How can the second move be before the first move?! 🙂
It is an implementation detail of how the
initializer_listis constructed, which normally wouldn’t matter because the extra moves would be elided.This creates a temporary
mystringusing the default constructor, then createsstd::initializer_list<mystring>with a single element, which is constructed using themystring(mystring&&)constructor with the temporary as its argument. That move construction printsMove. There’s another move done internally, printingMoveagain. Then theinitializer_listelement is copied into the vector as before, printingConst Reference.To answer your comment:
The extra move you see also happens in the first case, but in that case the type being moved is
const char*so it doesn’t printMovei.e. the statementstd::vector<mystring>{ expr };can be thought of as:When
exprisa(which is a non-const lvalue) creatingtmpprintsReferencethen creatingtmparrayprintsMovethen copyingtmparray[0]into the vector printsConst Reference.when
expris"hello"creating the elements oftmpdoesn’t print anything, and creatingtmparraycallsmystring(const char*)which doesn’t print anything, then copyingtmparray[0]into the vector printsConst Reference.When
exprismystring()creatingtmpprintsMovethen creatingtmparrayprintsMovethen copyingtmparray[0]into the vector printsConst ReferenceWith a two element braced-init-list like
{ expr1, expr2 }it can be though of as:So if you use
{ "hello", mystring() }you getMoveprinted once bytmp2andMoveprinted once bytmparrayandConst Referenceprinted twice by the elements ofv.Of course normally all of this would be elided away, so there are no unnecessary copies or moves.
-fno-elide-constructorsis not really useful except for finding out what would happen, but doesn’t actually happen!