# include <catch2/catch_test_macros.hpp>
# include <fmt/core.h>
# include <string>
# include <iostream>
# include <vector>
# include <cstdlib>
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 < A > 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 < A > 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 < A > emtest ;
emtest . reserve ( 10 ) ;
std : : vector < A > 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 ( ) ;
}