#include #include #include #include #include #include using namespace fmt; struct Counts { int default_constructor = 0; int value_constructor = 0; int copy_constructor = 0; int move_constructor = 0; int move_assign = 0; int copy_assign = 0; int destructor = 0; void reset() { default_constructor = 0; value_constructor = 0; copy_constructor = 0; move_constructor = 0; move_assign = 0; copy_assign = 0; destructor = 0; } int totals() { return default_constructor + value_constructor + copy_constructor + move_constructor + move_assign + copy_assign + destructor; } void dump() { fmt::println("default_constructor {} value_constructor {} copy_constructor {} move_constructor {} move_assign {} copy_assign", default_constructor, value_constructor, copy_constructor, move_constructor, move_assign, copy_assign, destructor); } }; Counts COUNTS; // Some heavy object struct A { int val = 0; A() { COUNTS.default_constructor++; std::cout << "default called constructor with: " << val << std::endl; } A(int val) : val(val) { COUNTS.value_constructor++; std::cout << "called constructor with: " << val << std::endl; } A(A const& other) { COUNTS.copy_constructor++; std::cout << "calling copy(A const& other): " << val << std::endl;; } A(A&& other) { COUNTS.move_constructor++; std::cout << "calling move(A&& other): " << val << std::endl;; } A& operator=(A&& other) { COUNTS.move_assign++; std::cout << "calling move(A&& other)=: " << val << std::endl;; return *this; } A& operator=(A const& other) { COUNTS.copy_assign++; std::cout << "calling copy(A const& other)=: " << val << std::endl;; return *this; } ~A() { COUNTS.destructor++; std::cout << "destructor called" << std::endl; } }; TEST_CASE("emplace move tests", "[emplace]") { A a{10}; std::vector test; test.reserve(10); // to prevent vector resizing and creating new objects on the way std::cout << "===== Emplacing Start =====\n"; // pass by l-value and will call copy constructor since it'll match // A(A const&) std::cout << "== 1. Emplace: Calling copy constructor \n"; test.emplace_back(a); // pass by r-value and will call move constructor since it'll match // A(A&&) std::cout << "== 2. Emplace: Calling move constructor \n"; test.emplace_back(std::move(a)); std::cout << "== 3. Emplace: Calling move constructor without direct constructor\n"; test.emplace_back(100); // pass by pr-value (pure r-value which is those value that has not come to an existance yet) // and will call move constructor since it'll match A(A&&). // "copy-elision" could be applied here but I don't know why compilers // refused to do it. Maybe how vectors are implemented and you need // to put the value in a slot so the compiler had to call the move constructor std::cout << "== 4. Emplace: Calling move constructor\n"; test.emplace_back(A{}); } TEST_CASE("pushback move tests", "[push_back]") { std::cout << "\n\n!!!!!!!!!! PUSH BACK alternative !!!!!!!!!!\n"; A a{20}; std::vector test; test.reserve(10); // to prevent vector resizing and creating new objects on the way std::cout << "===== Push Back Start =====\n"; // pass by l-value and will call copy constructor since it'll match // A(A const&) std::cout << "== 1. Push Back: Calling copy constructor \n"; test.push_back(a); // pass by r-value and will call move constructor since it'll match // A(A&&) std::cout << "== 2. Push Back: Calling move constructor \n"; test.push_back(std::move(a)); std::cout << "== 3. Push Back: Calling move constructor without direct constructor\n"; test.push_back(200); // pass by pr-value (pure r-value which is those value that has not come to an existance yet) // and will call move constructor since it'll match A(A&&). // "copy-elision" could be applied here but I don't know why compilers // refused to do it. Maybe how vectors are implemented and you need // to put the value in a slot so the compiler had to call the move constructor std::cout << "== 4. Push Back: Calling move constructor\n"; test.push_back(A{}); } TEST_CASE("proof you don't need emplace or std::move", "[proof]") { A a{20}; A b{30}; COUNTS.reset(); std::vector emtest; emtest.reserve(10); std::vector pbtest; pbtest.reserve(10); REQUIRE(COUNTS.totals() == 0); emtest.emplace_back(a); REQUIRE(COUNTS.totals() == 1); COUNTS.dump(); COUNTS.reset(); pbtest.push_back(b); REQUIRE(COUNTS.totals() == 1); COUNTS.dump(); COUNTS.reset(); // confirm these do the same number of calls emtest.emplace_back(A{}); REQUIRE(COUNTS.totals() == 3); COUNTS.dump(); COUNTS.reset(); pbtest.push_back(A{}); REQUIRE(COUNTS.totals() == 3); COUNTS.dump(); COUNTS.reset(); { REQUIRE(COUNTS.totals() == 0); A c{}; emtest.emplace_back(std::move(c)); REQUIRE(COUNTS.totals() == 2); COUNTS.dump(); COUNTS.reset(); A d{}; pbtest.push_back(std::move(d)); REQUIRE(COUNTS.totals() == 2); COUNTS.dump(); COUNTS.reset(); } COUNTS.reset(); emtest.emplace_back(200); REQUIRE(COUNTS.totals() == 1); COUNTS.dump(); COUNTS.reset(); pbtest.push_back(200); REQUIRE(COUNTS.totals() == 3); COUNTS.dump(); COUNTS.reset(); }