I’ve seen my colleague do the second snippet quite often. Why is this? I’ve tried adding print statements to track the ctors and dtors, but both seem identical.
std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ct1);
std::vector<ClassTest> vecClass2;
vecClass2.push_back(ClassTest());
ClassTest& ct2 = vecClass2.back();
ct2.blah = blah // set some stuff
...
PS. I’m sorry if the title is misleading.
Edit:
Firstly, thank you all for your responses.
I’ve written a small application using std::move. The results are surprising to me perhaps because I’ve done something wrong … would someone please explain why the “fast” path is performing significantly better.
#include <vector>
#include <string>
#include <boost/progress.hpp>
#include <iostream>
const std::size_t SIZE = 10*100*100*100;
//const std::size_t SIZE = 1;
const bool log = (SIZE == 1);
struct SomeType {
std::string who;
std::string bio;
SomeType() {
if (log) std::cout << "SomeType()" << std::endl;
}
SomeType(const SomeType& other) {
if (log) std::cout << "SomeType(const SomeType&)" << std::endl;
//this->who.swap(other.who);
//this->bio.swap(other.bio);
this->who = other.who;
this->bio = other.bio;
}
SomeType& operator=(SomeType& other) {
if (log) std::cout << "SomeType::operator=()" << std::endl;
this->who.swap(other.who);
this->bio.swap(other.bio);
return *this;
}
~SomeType() {
if (log) std::cout << "~SomeType()" << std::endl;
}
void swap(SomeType& other) {
if (log) std::cout << "Swapping" << std::endl;
this->who.swap(other.who);
this->bio.swap(other.bio);
}
// move semantics
SomeType(SomeType&& other) :
who(std::move(other.who))
, bio(std::move(other.bio)) {
if (log) std::cout << "SomeType(SomeType&&)" << std::endl;
}
SomeType& operator=(SomeType&& other) {
if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl;
this->who = std::move(other.who);
this->bio = std::move(other.bio);
return *this;
}
};
int main(int argc, char** argv) {
{
boost::progress_timer time_taken;
std::vector<SomeType> store;
std::cout << "Timing \"slow\" path" << std::endl;
for (std::size_t i = 0; i < SIZE; ++i) {
SomeType some;
some.who = "bruce banner the hulk";
some.bio = "you do not want to see me angry";
//store.push_back(SomeType());
//store.back().swap(some);
store.push_back(std::move(some));
}
}
{
boost::progress_timer time_taken;
std::vector<SomeType> store;
std::cout << "Timing \"fast\" path" << std::endl;
for (std::size_t i = 0; i < SIZE; ++i) {
store.push_back(SomeType());
SomeType& some = store.back();
some.who = "bruce banner the hulk";
some.bio = "you do not want to see me angry";
}
}
return 0;
}
Output:
dev@ubuntu-10:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
dev@ubuntu-10:~/Desktop/perf_test$ ./a.out
Timing "slow" path
3.36 s
Timing "fast" path
3.08 s
If we accept that your colleague’s snippet is wise, because ClassTest is expensive to copy, I would prefer:
I think it’s clearer, and it may well be more exception-safe. The
...code presumably allocates resources and hence could throw an exception (or else what’s making the fully-builtClassTestso expensive to copy?). So unless the vector really is local to the function, I don’t think it’s a good idea for it to be half-built while running that code.Of course this is even more expensive if
ClassTestonly has the defaultswapimplementation, but ifClassTestdoesn’t have an efficientswap, then it has no business being expensive to copy. So this trick perhaps should only be used with classes known to be friendly, rather than unknown template parameter types.As Gene says,
std::moveis better anyway, if you have that C++0x feature.If we’re worried about ClassTest being expensive to copy, though, then relocating the vector is a terrifying prospect. So we should also either:
dequeinstead of avector.