C++ Memory Management



Preamble

Memory Management from C++11 encompasses the way memory is dynamically allocated and unallocated through, mainly, the new and delete operators. Beyond, C++ makes on its own many data copies to exchange data between running scopes. For example, return statements copy data from callees to callers. As a result, the way data burdens memory requires developer control and clairvoyance as well.

Robust (sober) programming involves canonical and systematic support (e.g., best practice) in classes, which deal with dynamical memory: support for initialization, assignment, move…

Headlines
Initialization versus assignment

Rule(s)

Example Human_being.Cpp.zip 

// '.h' file:
class Human_being {
private:
	const std::string _nickname; // Non-dynamical field
	// 'const Human_being* _my_buddies[];' does not allow later assignment through 'new':
	const Human_being** _my_buddies;
public:
	Human_being(const std::string&, int = 5);
	Human_being(const Human_being&); // Copy constructor or initialization
	~Human_being(); // Required destructor
	const Human_being& operator=(const Human_being&); // Assignment redefinition
};

// '.cpp' file:
Human_being::Human_being(const std::string& nickname, unsigned my_friends_capacity) : _nickname(nickname) {
	_my_buddies = new const Human_being * [my_friends_capacity];
}

Human_being::Human_being(const Human_being& hb) : _nickname(hb._nickname) {
	_my_buddies = new const Human_being * [sizeof(hb._my_buddies) / sizeof(hb._my_buddies[0])];
	for (int i = 0; i < sizeof(hb._my_buddies) / sizeof(hb._my_buddies[0]); i++) {
		_my_buddies[i] = hb._my_buddies[i];
	}
}

Human_being::~Human_being() {
	delete[] _my_buddies;
}

const Human_being& Human_being::operator=(const Human_being& hb) {
	if (this != &hb) { // Weird case: 'Human_being hb; hb = hb;'
		this->~Human_being(); // A bit weird, but...
		this->_my_buddies = new const Human_being * [sizeof(hb._my_buddies) / sizeof(hb._my_buddies[0])];
		for (int i = 0; i < sizeof(hb._my_buddies) / sizeof(hb._my_buddies[0]); i++) {
			this->_my_buddies[i] = hb._my_buddies[i];
		}
	}
	return *this;
}

int main(int argc, char** argv) {
	Human_being Franck("Bab", 10);
	Human_being FranckBis = Franck; // Initialization!
	Human_being FranckTer("?");
	FranckTer = Franck; // Assignment!

	return 0;
}
rvalue” versus “lvalue

“Move” semantics

Rule(s)

Example (foundation) Rvalue.Cpp.zip 

// '.h' file:
class Human_being : … {
private:
    static const std::string _Default_nickname;
public:
    static Human_being Default_Human_being();
    …

// '.cpp' file:
const std::string Human_being::_Default_nickname = "?";

Human_being Human_being::Default_Human_being() {
	return Human_being(_Default_nickname); // Created object *IS COPIED* to caller context
}
…
// Compilation error:
// Human_being& who = Human_being("who"); // A "lvalue" cannot bind a "temporary" object, i.e., a "rvalue"...
// Copy elision through optimization, i.e., the move constructor *IS NOT* called:
Human_being&& who = Human_being("who"); // While a "rvalue" can!

// The 'Human_being' object returned by 'Human_being::Default_Human_being' is *MOVED* from callee context to caller context
// *WITHOUT* superfluous copy...
// This often was a compilation optimization that became a developer-controlled mechanism from C++11:
Human_being&& default_Human_being = Human_being::Default_Human_being(); // Debugger shows that copy *ISN'T* called...

Example (bypass copy) Rvalue.Cpp.zip 

// '.h' file:
class Human_being :  … {
private:
	static const std::string _Default_nickname;
public:
	static Human_being Default_Human_being();
	const std::string nickname;
private:
	Resource* _resource = nullptr;
public:
	Human_being(const std::string & = "?");
	Human_being(const Human_being&); // Copy constructor or initialization
	Human_being(Human_being&&) noexcept; // Move constructor
	virtual ~Human_being(); // Required destructor
	Human_being& operator=(Human_being const& /* <=> 'const Human_being&' */); // Copy assignment
	Human_being& operator=(Human_being&&) noexcept; // Move assignment
	void becomes(Human_being&&);
	Resource* const get_resource() const;
};

// '.cpp' file:
Human_being::Human_being(Human_being&& hb) noexcept : nickname(hb.nickname) {
	std::cout << "Move constructor from: " << hb.nickname << std::endl;
	// Move semantics:
	std::swap(_resource, hb._resource); // 'delete _resource;' on 'hb' is later done when "rvalue" is destroyed...
}
…
Human_being& Human_being::operator=(Human_being&& hb) noexcept {
	std::cout << "Move assignment from: " << hb.nickname << std::endl;
	const_cast<std::string&> (this->nickname) = hb.nickname;
	// Move semantics:
	std::swap(_resource, hb._resource); // 'delete _resource;' on 'hb' is later done when "rvalue" is destroyed...
	return *this;
}
…
Human_being someone;
// 'static Human_being GetInstance(const std::String& s) { Human_being hb(s); return hb; } // Deletion of 'hb'...
// "Move constructor" (return) and next "move assignment":
someone = Human_being::GetInstance("someone else...");

Rule(s)

Example (force move) Rvalue.Cpp.zip 

Human_being EmmanuelMacron("EmmanuelMacron");
Human_being president;
Resource* resource = president.get_resource();
president = std::move(EmmanuelMacron); // Move assignment ('_resource' fields are exchanged...)
assert(EmmanuelMacron.get_resource() == resource);

Rule(s)

Example Rvalue.Cpp.zip 

void Human_being::becomes(Human_being&& hb) { // 'hb' is "lvalue"!
    // Rule: "Things that are declared as rvalue reference can be lvalues or rvalues.
    // The distinguishing criterion is: if it has a name then it is an lvalue else it is an rvalue."
    *this = hb; // 'Human_being& Human_being::operator=(Human_being const& hb)' is called...
}
…
Human_being anybody;
// 'static Human_being GetInstance(const std::String& s) { Human_being hb(s); return hb; } // Deletion of 'hb'...
anybody.becomes(Human_being::GetInstance("anyone"));

Perfect forwarding

Rule(s)

Example (forwarding, reminder)

class A;
class B { A* _a; };
class A { B& _b; };

Rule(s)

Example (adapt between “lvalue” and “rvalue”: failure) Rvalue.Cpp.zip 

Human_being Human_being::GetClone(Human_being& hb) {
    /* Change 'hb' in some way...*/ Human_being _hb = hb; return _hb;
} // Deletion of '_hb'...
Human_being Human_being::GetClone(const Human_being& hb) {
    /* Change 'hb' in some way...*/ Human_being _hb = hb; return _hb;
} // Deletion of '_hb'...
Human_being Human_being::GetClone(Human_being&& hb) { 
    /* Change 'hb' in some way... */
    // 'std::forward' brings no value here since *THERE IS NO DEDUCTION FROM TEMPLATE INSTANTIATION*:
    Human_being _hb(std::forward<Human_being>(hb)); // Forwards "lvalue" either as "lvalue" or as "rvalue", depending on 'hb'...
    return _hb;
} // Deletion of '_hb'...
…
// 'GetClone(Human_being& hb)' does not work:
// Human_being attendee = Human_being::GetClone(Human_being::GetInstance("attendee")); // Compilation error: "lvalue" required...
// 'GetClone(const Human_being& hb)' makes it work;
Human_being attendee = Human_being::GetClone(Human_being::GetInstance("attendee")); // "rvalue" "disappears"...
Human_being attendee_ = Human_being::GetClone(Human_being::GetInstance("attendee_")); // "rvalue" passed, OK...

Rule(s)

Example (adapt between “lvalue” and “rvalue”: success) Rvalue.Cpp.zip 

template<typename O> O GetClone(O&& o) { // Global namespace...
    // Change 'o' in some way...
    O _o(std::forward<O>(o)); // Forwards "lvalue" either as "lvalue" or as "rvalue", depending on 'o'...
    return _o;
} // Deletion of '_o'...
…
Human_being FranckBarbier("FranckBarbier");
Human_being& Bab = FranckBarbier; // Copy constructor ("lvalue"): '_resource' is shared...
assert(FranckBarbier.get_resource() != nullptr && Bab.get_resource() != nullptr && FranckBarbier.get_resource() == Bab.get_resource());

Human_being trainer = ::GetClone/*<Human_being&>*/(FranckBarbier); // "lvalue"
assert(FranckBarbier.get_resource() != nullptr && trainer.get_resource() != nullptr && FranckBarbier.get_resource() != trainer.get_resource());

See also

The default and delete keywords may, from C++11, be associated with “implicit” functions: constructors (base, copy, move), destructor and assignment (copy, move)

Rule(s)

Example Default_delete.Cpp.zip 

'.h' file:
class My_class { // 'inline' is for compactness only!
private:
    // 'std::is_trivially_copyable<My_class>: false':
    // 'std::is_trivially_assignable<My_class, My_class>': false:
    // 'std::is_trivially_move_constructible<My_class>': false:
    std::string _resource;
    // To prevent verbose writing of 'My_class::My_class() {}' in '.cpp' file:
    My_class() = default; // 'std::is_trivially_constructible<My_class>': false
public:

    // 'std::is_constructible<My_class, std::string>': true':
    inline My_class(std::string s) : _resource(s) {};

    // 'std::is_copy_constructible<My_class>': false:
    // 'std::is_assignable<My_class, My_class>': false:
    // 'std::is_move_constructible<My_class, My_class>': true:
    inline My_class(My_class&& mc) : _resource(std::move(mc._resource)) {}

    My_class(const My_class&) = delete; // 'std::is_copy_constructible<My_class>': false && 'std::is_move_constructible<My_class, My_class>': false
    My_class& operator=(const My_class&) = delete; // 'std::is_assignable<My_class, My_class>': false

    // 'std::is_assignable<My_class, My_class>': true:
    inline My_class& operator=(My_class&& mc) { // Move assignment
        _resource = std::move(mc._resource);
        return *this;
    };

    // To prevent verbose writing of 'My_class::~My_class() {}' in '.cpp' file:
    virtual ~My_class() = default; // Polymorphic type => !triviality
};

// '.cpp' file:
My_class mc("mc");

std::cout << std::boolalpha << "'std::is_trivially_constructible<My_class>': " <<
            std::is_trivially_constructible<My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_constructible<My_class, int>': " <<
            std::is_constructible<My_class, std::string>::value << std::endl << std::endl; // 'true'

std::cout << std::boolalpha << "'std::is_trivially_copyable<My_class>': " <<
            std::is_trivially_copyable<My_class>::value << std::endl; // 'false'
// My_class mc_ = mc; // Compilation error because copy constructor has been deleted
std::cout << std::boolalpha << "'std::is_copy_constructible<My_class>': " <<
            std::is_copy_constructible<My_class>::value << std::endl << std::endl; // 'false'

My_class mc_("mc_");
// mc = mc_; // Compilation error because copy assignment has been deleted
mc = std::move(mc_); // Move assignment
mc = My_class("Move assignment"); // Move assignment

std::cout << std::boolalpha << "'std::is_trivially_assignable<My_class, My_class>': " <<
            std::is_trivially_assignable<My_class, My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_assignable<My_class, My_class>': " <<
            std::is_assignable<My_class, My_class>::value << std::endl << std::endl; // 'true'

My_class mc__(My_class("Move constructor"));
std::cout << std::boolalpha << "'std::is_trivially_move_constructible<My_class>': " <<
            std::is_trivially_move_constructible<My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_move_constructible<My_class>': " <<
            std::is_move_constructible<My_class>::value << std::endl; // 'true'

Example (stupid people?)

class X {
public:
    ~X() = delete;
};

// Later on, in '.cpp' file:
// X x; // Compilation error: somehow late but safe anyway...
std::string_view

Rule(s)

Example (load CSV file into memory) GTIN.Cpp.zip 

"code","brand","model","name","last_updated","gs1_country","gtinType","offers_count","min_price","min_price_compensation","currency","categories","url"
"9110200705009","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 180x200 cm Tissu -MN28763","1706101081492","AT","GTIN_13","1","673.99","2.6959600000000004","EUR","?","?"
"9110200704170","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 160x200 cm Tissu -MN83316","1706101081497","AT","GTIN_13","1","563.99","2.25596","EUR","?","?"
"9110200704187","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 160x200 cm Tissu -MN90540","1706101081500","AT","GTIN_13","1","640.99","2.5639600000000002","EUR","?","?"
"9110200704620","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 160x200 cm Tissu -MN59666","1706101081491","AT","GTIN_13","1","654.99","2.6199600000000003","EUR","?","?"
"9110200705030","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 180x200 cm Tissu -MN42472","1706101081499","AT","GTIN_13","1","693.99","2.7759600000000004","EUR","?","?"
"9110200701490","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 140x190 cm Tissu -MN93453","1706101081485","AT","GTIN_13","1","516.99","2.0679600000000002","EUR","?","?"
"9110200704651","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 160x200 cm Tissu -MN72052","1706101081501","AT","GTIN_13","1","429.99","1.7199600000000002","EUR","?","?"
"9110200703326","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 140x200 cm Tissu -MN86415","1706101081503","AT","GTIN_13","1","427.99","1.7119600000000004","EUR","?","?"
"9110200705535","OTHER","?","Lit Adulte - Lit à sommier tapissier avec matelas moelleux - Meuble de Chambre à Coucher - Noir 180x200 cm Tissu -MN80356","1706101081506","AT","GTIN_13","1","705.99","2.82396","EUR","?","?"

Note(s)

#include <cassert>
#include <filesystem> // 'std::filesystem::path'...
#include <fstream> // 'std::ifstream'...
#include <iostream>
#include <string> // 'std::getline'...
#include <string_view>

#include "GTIN.h"

std::ifstream::event_callback call_back_function = [](std::ios_base::event event, std::ios_base& stream, int index) -> void {
	if (event == std::ios_base::erase_event)
		std::cout << "std::ios_base::erase_event" << std::endl;
	};

std::filesystem::path const GTIN::Path = std::filesystem::path("/Users/franc/Desktop/GTIN/GTIN.csv");

std::list<std::string_view> GTIN::_Slice_line(const std::string& line) {
	std::list<std::string_view> sliced_line;
	std::string::const_iterator from = line.end(); // Invalid position...
	std::string::const_iterator to = line.end(); // Invalid position...
	for (std::string::const_iterator i = line.begin(); !(i == line.end()); i++) {
		to = *i == '"' && from != line.end() ? i : to;
		from = *i == '"' && from == line.end() ? i : from;
		if (from != line.end() && to != line.end()) {
			sliced_line.push_back(std::string_view{ ++from, to }); // Copy, but cheap...
			// Next round...
			from = line.end();
			to = line.end();
		}
	}
	return sliced_line; // Copy, but cheap...
}

GTIN::GTIN(const std::string& file_name) {
	std::ifstream ifstream;
	if (file_name != "")
		ifstream.open(file_name/*, std::ios_base::in*/);
	else
		ifstream.open(GTIN::Path/*, std::ios_base::in*/);
	if (not ifstream.is_open())
		throw std::ios::failure("Failed to open file....");
	assert(ifstream.is_open());
	ifstream.register_callback(call_back_function, 0); // Call back function on stream ('0' is custom data)...

	/* First line */
	std::string line;
	std::getline(ifstream, line, '\r'); // Windows!
	std::list<std::string_view> sliced_line = GTIN::_Slice_line(line);
	// for (auto& slice : sliced_line) std::cout << slice << '\t';

	while (std::getline(ifstream, line, '\r')) { // Fill in memory with data inside OS buffer...
		std::string line_copy = line; // Copy, expensive...
		_lines.push_back(line_copy); // Copy, expensive...
		// Bug, string views refer to positions in 'line_copy', which soon vanishes:
		// _data.push_back(GTIN::_Slice_line(line_copy));
		_data.push_back(GTIN::_Slice_line(_lines.back())); // Bug suppression...
	}

	ifstream.close(); // Useless, done automatically...

	// for (auto& datum : _data) { for (auto& item : datum) std::cout << item << '\t'; std::cout << std::endl << std::endl; }
}

int main() {
	// std::cout << "Path of working directory: " << std::filesystem::current_path() << std::endl;
	GTIN{};

	return 0;
}

Exercise