A repository where I'll put experiments learning, proving or disproving things about C++. These experiments will feature measurements, use Tracy to show behavior, and generally have the goal of simplifying normal C++ usage.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

213 lines
5.4 KiB

#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();
}