Memory Management from C++11 encompasses the way memory is dynamically allocated and unallocated through, mainly, the
newanddeleteoperators. Beyond, C++ makes on its own many data copies to exchange data between running scopes. For example,returnstatements 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…
default and delete keywordsstd::string_view (C++17)Rule(s)
- In C++, initialization and assignment are radically different. Initialization relies on the copy constructor while assignment is the possible redefinition of the
=operator.- Resource Acquisition Is Initialization -RAII- here… is the principle that object creation is the appropriate time for acquiring resources (dynamic memory for instance through
new). Robust programming involves the release ot these resources at destruction time.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)
- C++11 introduces the idea of “rvalue” opposite to that of “lvalue”. Formally, in C++14, C++17 and, C++20, it exists the distinction between pure “rvalue” (a.k.a. “prvalue”), “lvalue”, “xvalue”… Further detail here…
- To simplify, a “lvalue” is an expression that refers to a memory location and allows us to take the address of that memory location via the
&operator. In contrast, until C++11, a “rvalue” was not programmatically accessible even though it relates to some memory space.- C++11 changes the deal as follows: if
Xis any type thenX&&is called a “rvalue” reference toX.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)
- “Move” semantics may be forced…
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)
- Things that are declared as “rvalue” reference can be a “lvalue” or a “rvalue”. The distinguishing criterion is: if it has a name then it is a “lvalue” else it is a “rvalue”.
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)
- The definition of classes may refer to not-yet-defined classes: forwarding. This is especially true and crucial for template classes that deal with anonymous types, but implicitly require key characteristics owned by these types.
Example (forwarding, reminder)
class A; class B { A* _a; }; class A { B& _b; };Rule(s)
std::forward(here…) is a key C++ facility (based onstd::remove_reference) to adapt between “lvalue” and “rvalue”. Collapsing rules fromstd::remove_referenceare also described here…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)
std::forward(here…) relies on type deduction (a.k.a. “collapsing rules”) from template instantiation. A template-based version ofGetCloneis therefore required.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 defaultanddeletekeywords may, from C++11, be associated with “implicit” functions: constructors (base, copy, move), destructor and assignment (copy, move)Rule(s)
- Triviality like TriviallyCopyable is the general-purpose mechanism for assigning/copying/moving an object to another. Triviality is lost as soon as the type of interest, e.g.,
My_class, has, for instance, a resource attribute to be assigned/copied/moved, e.g.,std::string _resource;, which is itself not trivially copyable.- Beyond, if the type of interest is polymorphic (through its destructor, for instance) then
std::is_polymorphic<My_class>⇒ !std::is_trivially_copyable<My_class>, !std::is_trivially_assignable<My_class>, and !std::is_trivially_move_constructible<My_class>.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_viewRule(s)
std::string_view(#include <string_view>) objects are lightweight objects that refer tostd::stringobjects.- Principle:
std::stringobjects have to persist in memory. For example, extracting a sub-string may be viewed as creating a newstd::stringobject: the extracted memory part (old school) or referring to memory part embodying the extraction (std::string_viewobject). Of course, a key point is the “stability” in memory of the initialstd::stringobject.Example (load CSV file into memory) GTIN.Cpp.zip
![]()
" c o d e " , " b r a n d " , " m o d e l " , " n a m e " , " l a s t _ u p d a t e d " , " g s 1 _ c o u n t r y " , " g t i n T y p e " , " o f f e r s _ c o u n t " , " m i n _ p r i c e " , " m i n _ p r i c e _ c o m p e n s a t i o n " , " c u r r e n c y " , " c a t e g o r i e s " , " u r l " " 9 1 1 0 2 0 0 7 0 5 0 0 9 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 8 0 x 2 0 0 c m T i s s u - M N 2 8 7 6 3 " , " 1 7 0 6 1 0 1 0 8 1 4 9 2 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 6 7 3 . 9 9 " , " 2 . 6 9 5 9 6 0 0 0 0 0 0 0 0 0 0 4 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 4 1 7 0 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 6 0 x 2 0 0 c m T i s s u - M N 8 3 3 1 6 " , " 1 7 0 6 1 0 1 0 8 1 4 9 7 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 5 6 3 . 9 9 " , " 2 . 2 5 5 9 6 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 4 1 8 7 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 6 0 x 2 0 0 c m T i s s u - M N 9 0 5 4 0 " , " 1 7 0 6 1 0 1 0 8 1 5 0 0 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 6 4 0 . 9 9 " , " 2 . 5 6 3 9 6 0 0 0 0 0 0 0 0 0 0 2 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 4 6 2 0 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 6 0 x 2 0 0 c m T i s s u - M N 5 9 6 6 6 " , " 1 7 0 6 1 0 1 0 8 1 4 9 1 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 6 5 4 . 9 9 " , " 2 . 6 1 9 9 6 0 0 0 0 0 0 0 0 0 0 3 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 5 0 3 0 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 8 0 x 2 0 0 c m T i s s u - M N 4 2 4 7 2 " , " 1 7 0 6 1 0 1 0 8 1 4 9 9 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 6 9 3 . 9 9 " , " 2 . 7 7 5 9 6 0 0 0 0 0 0 0 0 0 0 4 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 1 4 9 0 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 4 0 x 1 9 0 c m T i s s u - M N 9 3 4 5 3 " , " 1 7 0 6 1 0 1 0 8 1 4 8 5 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 5 1 6 . 9 9 " , " 2 . 0 6 7 9 6 0 0 0 0 0 0 0 0 0 0 2 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 4 6 5 1 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 6 0 x 2 0 0 c m T i s s u - M N 7 2 0 5 2 " , " 1 7 0 6 1 0 1 0 8 1 5 0 1 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 4 2 9 . 9 9 " , " 1 . 7 1 9 9 6 0 0 0 0 0 0 0 0 0 0 2 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 3 3 2 6 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 4 0 x 2 0 0 c m T i s s u - M N 8 6 4 1 5 " , " 1 7 0 6 1 0 1 0 8 1 5 0 3 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 4 2 7 . 9 9 " , " 1 . 7 1 1 9 6 0 0 0 0 0 0 0 0 0 0 4 " , " E U R " , "?", "?" " 9 1 1 0 2 0 0 7 0 5 5 3 5 " , " O T H E R " ,"?"," L i t A d u l t e - L i t às o m m i e r t a p i s s i e r a v e c m a t e l a s m o e l l e u x - M e u b l e d e C h a m b r e à C o u c h e r - N o i r 1 8 0 x 2 0 0 c m T i s s u - M N 8 0 3 5 6 " , " 1 7 0 6 1 0 1 0 8 1 5 0 6 " , " A T " , " G T I N _ 1 3 " , " 1 " , " 7 0 5 . 9 9 " , " 2 . 8 2 3 9 6 " , " E U R " , "?", "?"Note(s)
- Lines 10-13 and 43 are an example of native Observer Model-View-Controller -MVC- design patterns in C++
#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
- Replace copy by move (lines 52-53)