tddd38 726g82 advanced programming in c
play

TDDD38/726G82 - Advanced programming in C++ Sum Types in C++ - PowerPoint PPT Presentation

TDDD38/726G82 - Advanced programming in C++ Sum Types in C++ Christoffer Holm Department of Computer and informaon science 1 Intro 2 Union 3 STL types 4 Implementaon 5 Second Implementaon 1 Intro 2 Union 3 STL types 4


  1. 30 / 66 Implementa�on Non-trivial union-like classes union.cc:14:12: error: use of deleted function 'my_union::my_union()' my_union u; ^ union.cc:3:8: note: 'my_union::my_union()' is implicitly deleted because the default definition would be ill-formed: struct my_union ^~~~~~~~ union.cc:14:12: error: use of deleted function 'my_union::~my_union()' my_union u; ^ union.cc:3:8: note: 'my_union::~my_union()' is implicitly deleted because the default definition would be ill-formed: struct my_union ^~~~~~~~

  2. 31 / 66 Implementa�on Non-trivial union-like classes ‚ the compiler is unable to generate constructors and destructors for unions ‚ this is because the compiler is unable to determine if a fields destructor and constructor should be called ‚ since only one type can be ac�ve at once the compiler can’t know which one it is (if any) ‚ due to this, we must define special member func�ons ourselves

  3. 32 / 66 Implementa�on Non-trivial union-like classes struct my_union { int main() my_union() : n{0} { } { ~my_union() { } my_union u{}; union { u.s = "hello"; int n; std::string s; cout << u.s << endl; }; } };

  4. 32 / 66 Implementa�on Non-trivial union-like classes struct my_union { int main() my_union() : n{0} { } S { e g ~my_union() { } my_union u{}; m e union n t a { u.s = "hello"; � o int n; n F a std::string s; cout << u.s << endl; u l t }; } };

  5. 32 / 66 Implementa�on Non-trivial union-like classes struct my_union { int main() my_union() : n{0} { } { W ~my_union() { } my_union u{}; h y union t h { u.s = "hello"; o u g int n; h ? std::string s; cout << u.s << endl; ! }; } };

  6. 33 / 66 Implementa�on Non-trivial union-like classes ‚ only one field is ac�ve at once ‚ in the constructor we ini�alize n ‚ thus leaving s unini�alized ‚ when we assign to s we are assigning to an unini�alized string

  7. 33 / 66 Implementa�on Non-trivial union-like classes ‚ assignment assumes that both strings are correctly ini�alized ‚ we would have to call a constructor on s ‚ ... but this can only be done at ini�alizia�on? ‚ there is one other way to call constructors a�er the fact!

  8. 34 / 66 Implementa�on Placement new struct my_union int main() { { my_union() : n{0} { } my_union u{}; ~my_union() { } union new (&u.s) std::string; { u.s = "hello"; int n; std::string s; cout << u.s << endl; }; } };

  9. 35 / 66 Implementa�on Placement new ‚ placement new is a call to new with an extra parameter ‚ this extra parameter is a pointer to memory where an object should be placed ‚ this will not allocate any memory ‚ but will instead call a constructor of a specified type on the specified memory loca�on ‚ this is a way to manually handle life�me without any dynamic alloca�ons!

  10. 36 / 66 Implementa�on But what about destruc�on? int main() { my_union u{}; // call constructor new (&u.s) std::string; u.s = "hello"; cout << u.s << endl; // explicitly call destructor u.s.std::string::~string(); }

  11. 37 / 66 Implementa�on But what about destruc�on? ‚ unions does not track which field is ac�ve ‚ so the compiler will be unable to call the appropriate destructor ‚ the my_union destructor is unable to know which field is ac�ve ‚ therefore we have to manually call the destructor of s to ensure that no memory leaks occur ‚ calling the string destructor will only work if the union actually contains a string

  12. 38 / 66 Implementa�on Extra note ‚ u.s.std::string::~string() is the way we call the destructor ‚ if we have using std::string or using namespace std in our code we can simplify this to u.s.~string() ‚ std::string is in reality an alias for std::basic_string<char> so we can also write u.s.~basic_string()

  13. 39 / 66 Implementa�on OK, but how do I get correct destruc�on automa�cally? struct my_union { my_union() : n{0}, tag{INT} { } ~my_union() { } union { int n; std::string s; }; enum class Type { INT, STRING }; Type tag; };

  14. 40 / 66 Implementa�on OK, but how do I get correct destruc�on automa�cally? ‚ the only way to correctly destroy objects is if we ourselves keep track of what the current type is ‚ we create a so called tagged union ‚ we have some kind of data member that tracks what the current type is stored ‚ we will of course have to update this tag whenever we change the type

  15. 41 / 66 Implementa�on Now we are ready for our own implementa�on class Variant { public: // ... private: enum class Type { INT, STRING }; Type tag; union { int n; string s; }; };

  16. 41 / 66 Implementa�on Now we are ready for our own implementa�on class Variant { public: Variant(int n = 0); Variant(string const& s); ~Variant(); Variant& operator=(int other) &; Variant& operator=(string const& other) &; int& num(); string& str(); // ... };

  17. 42 / 66 Implementa�on Union-based implementa�on ‚ we create our variant as a tagged union ‚ use the tag data member to keep track of which type is currently stored ‚ we have assignment and ge�ers as our interface ‚ will have to always check the type before performing opera�ons

  18. 43 / 66 Implementa�on Constructors Variant::Variant(int n) : n{n}, tag{Type::INT} { } Variant::Variant(string const& s) : s{s}, tag{Type::STRING} { }

  19. 44 / 66 Implementa�on Constructors ‚ the constructors will ini�alize the appropriate field in the union ‚ they will also ini�alize tag to the appropriate value

  20. 45 / 66 Implementa�on Destructor Variant::~Variant() { if (tag == Type::STRING) { s.~string(); } }

  21. 46 / 66 Implementa�on Destructor ‚ if the currently assigned value is of type int then nothing needs to be done ‚ however; if the ac�ve type is string we have to manually call the destructor on that field

  22. 47 / 66 Implementa�on Assignment operators Variant& Variant::operator=(int other) & { if (tag == Type::STRING) { s.~string(); } n = other; tag = Type::INT; return *this; }

  23. 47 / 66 Implementa�on Assignment operators Variant& Variant::operator=(string const& other) & { if (tag == Type::INT) { new (&s) string; } s = other; tag = Type::STRING; return *this; }

  24. 48 / 66 Implementa�on Assignment operators ‚ if we are assigning a string we must guarantee that s is an ini�alized string object ‚ if the ac�ve field is not string in that case we have to use placement new to construct a string in s ‚ if we are assigning an int we must poten�ally destroy s (if s was the previous ac�ve field) ‚ therefore we check the type and call the destructor if necessary

  25. 49 / 66 Implementa�on Ge�ers int& Variant::num() { if (tag == Type::INT) { return n; } throw /* ... */; }

  26. 49 / 66 Implementa�on Ge�ers string& Variant::str() { if (tag == Type::STRING) { return s; } throw /* ... */; }

  27. 50 / 66 Implementa�on Ge�ers ‚ the ge�ers should only return valid values ‚ therefore we throw some kind of excep�on if the ac�ve field is of incorrect type

  28. 51 / 66 Implementa�on Test program Variant v{}; // will set n = 0 cout << v.num() << endl; // active field is int v = 5; cout << v.num() << endl; // active field is int, we must // construct a string inside the variant v = "this is a long string"; cout << v.str() << endl; // the destructor must destroy the string here

  29. 1 Intro 2 Union 3 STL types 4 Implementa�on 5 Second Implementa�on

  30. 53 / 66 Second Implementa�on Placement new std::string s{}; char data[sizeof(std::string)]; union { int n; std::string s; } u; int array[sizeof(std::string) / sizeof(int)]; int i{}; new (&s) std::string; // OK new (data) std::string; // OK new (&u.s) std::string; // OK new (array) std::string; // NOT OK new (&i) std::string; // NOT OK

  31. 54 / 66 Second Implementa�on Placement new ‚ We can place our object in any memory that is; ‚ a union ‚ a char array with enough space ‚ or an object of the same type as the one we are trying to construct

  32. 55 / 66 Second Implementa�on Placement new in C-arrays char data[sizeof(std::string)]; std::string* p {new (data) std::string}; *p = "hello world"; p->~string();

  33. 56 / 66 Second Implementa�on Second version (no union ) class Variant { public: // ... private: enum class Type { INT, STRING }; char data[sizeof(string)]; Type tag; };

  34. 56 / 66 Second Implementa�on Second version (no union ) class Variant { public: Variant(int n = 0); Variant(string const& s); ~Variant(); Variant& operator=(int other) &; Variant& operator=(string const& other) &; int& num(); string& str(); // ... };

  35. 57 / 66 Second Implementa�on Constructors Variant::Variant(int n) : data{}, tag{Type::INT} { new (data) int{n}; }

  36. 57 / 66 Second Implementa�on Constructors Variant::Variant(string const& s) : data{}, tag{Type::STRING} { new (data) string{s}; }

  37. 58 / 66 Second Implementa�on Now, how do we retrieve our objects from the array? *reinterpret_cast<string*>(&data)

  38. 58 / 66 Second Implementa�on Now, how do we retrieve our objects from the array? U n d e fi n e d *reinterpret_cast<string*>(&data) B e h a v i o u r

  39. 59 / 66 Second Implementa�on Aliasing int x{}; // aliases to x int* p{&x}; int& r{x}; // modifying x through aliases *p = 5; // OK r = 7; // OK

  40. 59 / 66 Second Implementa�on Aliasing int x{}; float* p{reinterpret_cast<float*>(&x)}; *p = 3.7; // NOT OK

  41. 60 / 66 Second Implementa�on Strict aliasing rule An object of type T can be aliased if the alias has one of the following types; ‚ T* ‚ T& ‚ char* ‚ ( unsigned char* and std::byte* )

  42. 61 / 66 Second Implementa�on Strict aliasing rule accessing objects through pointers or references is known as aliasing . ‚ so when aliasing an object of type T the following must be true; ‚ must be accessed through a T pointer or reference ‚ or must be accessed through a char pointer ‚ otherwise this is undefined behaviour This is known as the strict aliasing rule

  43. 62 / 66 Second Implementa�on The fix *std::launder(reinterpret_cast<string*>(&data));

  44. 63 / 66 Second Implementa�on std::launder ‚ std::launder is defined in <new> ‚ tell the compiler that it must ignore the strict aliasing rule in this case ‚ Note: only correct if we are trying to point to an actually constructed object of the specified type

  45. 64 / 66 Second Implementa�on Ge�ers int& Variant::num() { if (tag == Type::INT) { return *std::launder( reinterpret_cast<int*>(&data)); } throw /* ... */; }

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend