Memory Management from C++11 encompasses the way memory is dynamically allocated and unallocated through, mainly, the
new
anddelete
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…
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
X
is 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_reference
are 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 ofGetClone
is 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 default
anddelete
keywords 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_view
Rule(s)
std::string_view
(#include <string_view>
) objects are lightweight objects that refer tostd::string
objects.- Principle:
std::string
objects have to persist in memory. For example, extracting a sub-string may be viewed as creating a newstd::string
object: the extracted memory part (old school) or referring to memory part embodying the extraction (std::string_view
object). Of course, a key point is the “stability” in memory of the initialstd::string
object.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)