Index |
For executing example programs, GCC/C++20 compiler was used (C++20 support is experimental as per GCC website). | ||||||||||||||||||||||||
1) C++ versions, documentation |
Below link lists various C++ versions and GCC support for the same:
gcc.gnu.org At the time of writing this article, GCC supported C++17; to use C++20, put "-std=c++20" in command line. By checking "__cplusplus", we can determine support for the particular C++ version. Below is a snippet code to check C++20 support. if (__cplusplus >= 202002L) { std::cout << "C++20 supported." << std::endl; } else { std::cout << "C++20 *not* supported." << std::endl; } Below are values of __cplusplus (Modern C++):
C++ standard is available here at isocpp.org (for purchase). If you are looking for syntax, standard library, and such details, you may refer en.cppreference.com, cplusplus.com or devdocs.io. | ||||||||||||||||||||||||
2) Books on OOP and C++ |
For introduction to Object Oriented Programming, you may refer
Intro to OOP by Timothy Budd For detailed C++ (and history/origin), you may refer The C++ Programming Language (4th edition) by Bjarne Stroustrup (covers C++11) For overview of C++, you may refer, Tour of C++ by Bjarne Stroustrup (2nd edition covered till C++17 and 3rd edition covers till C++20) For C++ policy based design, you may refer, Modern C++ Design by Andrei Alexandrescu For practical understanding of C++, you may refer, Effective Modern C++ (C++11 and C++14) by Scott Meyers | ||||||||||||||||||||||||
3) Versions and features |
This is a brief list of features.
Please check Glossary if there is an example here.
| ||||||||||||||||||||||||
4) Object Oriented Programming keywords/concepts |
Few basic concepts:
| ||||||||||||||||||||||||
5) Hello World ! |
This one,
#include <iostream> int main() { std::cout << "Hello World !" << std::endl; return 0; Or this one, #include <iostream> using namespace std; int main() { cout << "Hello World !" << endl; return 0; } Or this one, #include <iostream> using std::cout; using std::endl; int main() { cout << "Hello World !" << endl; return 0; }
| ||||||||||||||||||||||||
6) class, constructor, and destructor |
Example of two classes,
#include <iostream> class human { private: //nothing so far protected: //nothing so far public: //nothing so far }; class man : public human { public: man() // constructor { std::cout << "I am born" << std::endl; } ~man() // destructor { std::cout << "I am dying" << std::endl; } }; int main() { man Man1; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
7) namespace |
Below are few methods to access names from the namespaces.
Method 1 (preferred) #include <iostream> namespace human { class man { public: man() { std::cout << "I am born" << std::endl; } ~man() { std::cout << "I am dying" << std::endl; } }; } int main() { human::man Man1; std::cout << "----------" << std::endl; return 0; } Method 2 #include <iostream> namespace human { class man { public: man() { std::cout << "I am born" << std::endl; } ~man() { std::cout << "I am dying" << std::endl; } }; } int main() { using namespace human; man Man1; std::cout << "----------" << std::endl; return 0; } Method 3 #include <iostream> namespace human { class man { public: man() { std::cout << "I am born" << std::endl; } ~man() { std::cout << "I am dying" << std::endl; } }; } int main() { using human::man; man Man1; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
8) Concept of "value" |
| ||||||||||||||||||||||||
9) Invariant |
Invariant is a condition that is assumed to be true for a class,
and it is usually verified during the object construction.
For example, container object like vector cannot be created with negative number of elements. | ||||||||||||||||||||||||
10) class tree/hierarchy |
Both man and woman are derived from base class human.
In below example, we have also used "nullptr".
#include <iostream> class human { public: virtual void who_are_you(void) { std::cout << "I am a human" << std::endl; } }; class man : public human { public: void who_are_you(void) { std::cout << "I am a man" << std::endl; } }; class woman : public human { public: void who_are_you(void) { std::cout << "I am a woman" << std::endl; } }; int main() { man Man1; woman Woman1; human* ptr_human = nullptr; ptr_human = &Man1; ptr_human->who_are_you(); ptr_human = &Woman1; ptr_human->who_are_you(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
11) Multiple inheritance |
Bjarne - "Deriving directly from more than one class is usually called multiple inheritance."
#include <iostream> #include <string> class man { public: man() // constructor { std::cout << "man()" << std::endl; } ~man() // destructor { std::cout << "~man()" << std::endl; } std::string how_are_you(void) { return "I am good."; } }; class woman { public: woman() // constructor { std::cout << "woman()" << std::endl; } ~woman() // destructor { std::cout << "~woman()" << std::endl; } std::string how_are_you(void) { return "I am good."; } }; class indian { public: enum class region_type { north, east, south, west }; indian() // constructor { std::cout << "indian()" << std::endl; } indian(region_type ip_region) // constructor { region = ip_region; std::cout << "indian()" << std::endl; } ~indian() // destructor { std::cout << "~indian()" << std::endl; } std::string where_are_you_from(void) { switch(region) { case region_type::north: return "I am from North India."; case region_type::east: return "I am from East India."; case region_type::south: return "I am from South India."; case region_type::west: return "I am from West India."; default: break; } return "I am from India."; } private: region_type region; }; class indian_man : public man, public indian // multiple inheritance { public: using indian::indian; // inheriting constructors from indian indian_man() // constructor { std::cout << "indian_man()" << std::endl; } ~indian_man() // destructor { std::cout << "~indian_man()" << std::endl; } }; class indian_woman : public woman, public indian // multiple inheritance { public: using indian::indian; // inheriting constructors from indian indian_woman() // constructor { std::cout << "indian_woman()" << std::endl; } ~indian_woman() // destructor { std::cout << "~indian_woman()" << std::endl; } }; int main() { indian_man Man(indian::region_type::west); std::cout << "Anchor: How are you ?" << std::endl; std::cout << "Man: " << Man.how_are_you() << std::endl; std::cout << "Anchor: Where are you from ?" << std::endl; std::cout << "Man: " << Man.where_are_you_from() << std::endl; indian_woman Woman(indian::region_type::east); std::cout << "Anchor: How are you ?" << std::endl; std::cout << "Woman: " << Woman.how_are_you() << std::endl; std::cout << "Anchor: Where are you from ?" << std::endl; std::cout << "Woman: " << Woman.where_are_you_from() << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
12) static in a class |
Static variables and functions have same meaning as in C.
#include <iostream> class human { private: static int population_ctr; protected: //nothing so far public: human() { ++population_ctr; } ~human() { --population_ctr; } static int population(void) { return population_ctr; } }; int human::population_ctr = 0; class man : public human { public: man() // constructor { std::cout << "I am born" << std::endl; } ~man() // destructor { std::cout << "I am dying" << std::endl; } }; int main() { man Man1; std::cout << "Population is " << human::population() << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
13) protected variable in a class |
private variables are *not* accessible to derived classes, but protected variables are.
#include <iostream> class human { private: static unsigned int dna_stamp_ctr; protected: unsigned int dna_stamp; public: human() { dna_stamp = ++dna_stamp_ctr; } }; unsigned int human::dna_stamp_ctr = 0; class man : public human { public: man() // constructor { std::cout << "[DNA# " << dna_stamp << "] I am born" << std::endl; } ~man() // destructor { std::cout << "I am dying" << std::endl; } }; int main() { man Man1; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
14) More on Constructor |
Below example illustrate shows how constructors are called
and how to call parent class constructor explicitly.
#include <iostream> #include <string> class human { private: //nothing so far protected: std::string name; public: human() { std::cout << "human()" << std::endl; } ~human() { std::cout << "~human()" << std::endl; } }; class man : public human { public: man() { std::cout << "man()" << std::endl; } man(std::string ip_name) { name = ip_name; std::cout << "man(std::string)" << std::endl; } ~man() { std::cout << "~man()" << std::endl; } }; class boy : public man { public: enum class hobby_type { sport, music, art }; boy() { std::cout << "boy()" << std::endl; } boy(std::string ip_name) : man(ip_name) // explicit call to parent class constructor { std::cout << "boy(std::string)" << std::endl; } boy(std::string ip_name, hobby_type ip_hobby) : man(ip_name), hobby(ip_hobby) { std::cout << "boy(std::string, hobby_type)" << std::endl; } ~boy() { std::cout << "~boy()" << std::endl; } private: hobby_type hobby; }; int main() { std::cout << "Creating boy ... " << std::endl; boy Boy_with_no_name; std::cout << "Creating boy with name ... " << std::endl; boy Boy_with_name("Boy"); std::cout << "Creating boy with name and hobby ... " << std::endl; boy Boy_with_hobby("Sporty boy", boy::hobby_type::sport); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
15) Initialisation with "{}" |
C++11 introduced "{}" syntax for initialisation.
#include <iostream> #include <string> class human { private: //nothing so far protected: std::string name; public: //nothing so far }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; class boy : public man { public: enum class hobby_type { sport, music, art }; boy(std::string ip_name, hobby_type ip_hobby) : man(ip_name), hobby(ip_hobby) { std::cout << "'" << name << "' with a hobby '"; switch(hobby) { case boy::hobby_type::sport: std::cout << "Sport"; break; case boy::hobby_type::music: std::cout << "Music"; break; case boy::hobby_type::art: std::cout << "Art"; break; } std::cout << "'" << std::endl; } private: hobby_type hobby; }; int main() { int basic_integer_1( 1 ); int basic_integer_2 = { 2 }; int basic_integer_3{ 3 }; int basic_integer_4 = 4; std::cout << "basic_integer_1 = " << basic_integer_1 << std::endl; std::cout << "basic_integer_2 = " << basic_integer_2 << std::endl; std::cout << "basic_integer_3 = " << basic_integer_3 << std::endl; std::cout << "basic_integer_4 = " << basic_integer_4 << std::endl; boy Boy1_with_hobby("Sporty boy", boy::hobby_type::sport); boy Boy2_with_hobby = {"Music loving boy", boy::hobby_type::music}; boy Boy3_with_hobby{"Artist boy", boy::hobby_type::art}; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
16) Delegating constructor |
Instead of copying code from other constructor, we can simply call the other constructor.
Example also shows a way to assign default values to class variable, this is known as in-class member initialiser. #include <iostream> #include <string> class human { private: //nothing so far protected: std::string name = "unnamed human"; // in-class member initialiser public: //nothing so far }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; class boy : public man { public: enum class hobby_type { none, sport, music, art }; boy(std::string ip_name, hobby_type ip_hobby) : man(ip_name), hobby(ip_hobby) { std::cout << "'" << name << "' with a hobby '"; switch(hobby) { case boy::hobby_type::none: std::cout << "none"; break; case boy::hobby_type::sport: std::cout << "Sport"; break; case boy::hobby_type::music: std::cout << "Music"; break; case boy::hobby_type::art: std::cout << "Art"; break; } std::cout << "'" << std::endl; } boy() : boy{"John Doe Junior", hobby_type::none} // delegating constructor or forwarding constructor { } private: hobby_type hobby; }; int main() { boy Someone; boy Boy1_with_hobby("Sporty boy", boy::hobby_type::sport); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
17) Inheriting the constructors |
Instead re-implementing, derived class may chose to re-use the parent class constructors.
#include <iostream> #include <string> class human { private: //nothing so far protected: std::string name; public: human() { std::cout << "human()" << std::endl; } ~human() { std::cout << "~human()" << std::endl; } }; class man : public human { public: man() { std::cout << "man()" << std::endl; } man(std::string ip_name) { name = ip_name; std::cout << "man(std::string)" << std::endl; } ~man() { std::cout << "~man()" << std::endl; } }; class boy : public man { public: using man::man; // inheriting constructors from parent class }; int main() { std::cout << "Creating boy ... " << std::endl; boy Boy_with_no_name; std::cout << "Creating boy with name ... " << std::endl; boy Boy_with_name("Boy"); // this would have failed in compilation had we not inherited the constructors std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
18) Return by reference, "&" |
Return by reference is risky; use with caution.
#include <iostream> #include <string> class person { private: std::string name; public: person() {} person(std::string ip_name) : name(ip_name) {} std::string& name_is(void) { return name; } // return by reference }; int main() { person unknown_guy; unknown_guy.name_is() = "John Doe"; std::cout << "We got " << unknown_guy.name_is() << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
19) virtual function in a class |
Both virtual and non-virtual functions can be overridden in derived class.
However, behaviour depend on the type of object (base class or derived class) as illustrated below,
#include <iostream> class human { public: void who_are_you_nonvirtual(void) { std::cout << "I am a human" << std::endl; } virtual void who_are_you_virtual(void) { std::cout << "I am a human" << std::endl; } }; class man : public human { public: void who_are_you_nonvirtual(void) { std::cout << "I am a man" << std::endl; } void who_are_you_virtual(void) { std::cout << "I am a man" << std::endl; } }; int main() { man Man1; human* ptr_human = &Man1; Man1.who_are_you_nonvirtual(); ptr_human->who_are_you_nonvirtual(); Man1.who_are_you_virtual(); ptr_human->who_are_you_virtual(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
20) Forcing method (function) implementation with virtual |
Setting to zero will force derived classes to provide implementation for virtual functions of base class.
Below example also shows use of "string" from standard library.
#include <iostream> #include <string> class human { protected: std::string name; public: virtual void set_name(std::string) = 0; virtual std::string get_name(void) = 0; }; class man : public human { public: void set_name(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Man1; Man1.set_name("Man1"); std::cout << "Man1's name is " << Man1.get_name() << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
21) functional/function |
Standard library functional/function is almost like function pointer,
that we use in C for storing callbacks.
#include <iostream> #include <map> #include <functional> class state { }; class engine { public: state* current_state; }; class event { }; state running; state stopped; event start; event stop; state* start_engine(state& s, event& e) { if( (&s == &stopped) && (&e == &start) ) { std::cout << "State: Stopped, Event: Start" << std::endl; std::cout << "Running now" << std::endl; return &running; } return &s; } state* stop_engine(state& s, event& e) { if( (&s == &running) && (&e == &stop) ) { std::cout << "State: Running, Event: Stop" << std::endl; std::cout << "Stopped now" << std::endl; return &stopped; } return &s; } std::function<state*(state&, event&)> state_machine[2] = { start_engine, stop_engine }; int main() { engine automobile_engine; automobile_engine.current_state = &stopped; auto number_of_start_stop_tests = 3; for(auto counter = 1; counter <= number_of_start_stop_tests; ++counter) { std::cout << "--- Iteration " << counter << " ---" << std::endl; if(automobile_engine.current_state == &stopped) automobile_engine.current_state = state_machine[0](*(automobile_engine.current_state), start); if(automobile_engine.current_state == &running) automobile_engine.current_state = state_machine[1](*(automobile_engine.current_state), stop); } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
22) Overloading the operator |
Below code illustrates overloading stream operator "<<" (with and without friend).
With friend #include <iostream> #include <string> class human { protected: std::string name; public: virtual void set_name(std::string) = 0; virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: void set_name(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Man1; Man1.set_name("Man1"); std::cout << "Man1's name is " << Man1 << std::endl; std::cout << "----------" << std::endl; return 0; }
Without friend #include <iostream> #include <string> class human { protected: std::string name; public: virtual void set_name(std::string) = 0; virtual std::string get_name(void) = 0; }; std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.get_name(); return out_stream; } class man : public human { public: void set_name(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Man1; Man1.set_name("Man1"); std::cout << "Man1's name is " << Man1 << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
23) operator "=" and copy constructor |
There are two ways in which we can create a copy of an object.
One with assignment operator and another one with a copy constructor. If not explicitly defined, compiler will supply both of the above implicitly. human Human1; human Human2(Human1); // copy constructor human Human3; Human3 = Human1; // assignment operator | ||||||||||||||||||||||||
24) Restricting operator overloading |
If we do not want to allow copy of humans, we can enforce it.
#include <iostream> #include <string> class human { protected: std::string name; public: virtual void set_name(std::string) = 0; virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } void operator=(human& cp_human) = delete; }; class man : public human { public: void set_name(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Man1; Man1.set_name("Man1"); std::cout << "Man1's name is " << Man1 << std::endl; man Man2; Man2 = Man1; std::cout << "Man2's name is " << Man2 << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
25) Using vector template |
Standard library has a "template" for "vector".
As the name suggests, it is an array.
There is an interesting keyword "auto". Variable created with auto automatically takes appropriate type based on the context. #include <iostream> #include <string> #include <vector> class human { protected: std::string name; public: virtual void set_name(std::string) = 0; virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } void operator=(human& cp_human) = delete; }; class man : public human { public: void set_name(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { const std::int8_t number_of_waiters = 3; std::vector<man> Waiters(number_of_waiters); Waiters[0].set_name("Waiter1"); Waiters[1].set_name("Waiter2"); Waiters[2].set_name("Waiter3"); std::cout << "We have below waiters ... " << std::endl; for(auto ctr=0; ctr < number_of_waiters/*alternatively, Waiters.size()*/; ++ctr) { std::cout << "\t" << Waiters[ctr] << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
26) Using map and its iterator |
map gives us a way to have non-numeric key as index.
map is implemented as balanced binary tree.
iterator allows us to traverse the (arranged) objects. #include <iostream> #include <string> #include <map> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } void operator=(human& cp_human) = delete; }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Customer1("Customer1"), Customer2("Customer2"), Customer3("Customer3"); std::map<std::string, unsigned int> restaurant_orders; unsigned int order_ctr = 0; restaurant_orders[Customer2.get_name()] = ++order_ctr; //1 restaurant_orders[Customer1.get_name()] = ++order_ctr; //2 restaurant_orders[Customer3.get_name()] = ++order_ctr; //3 std::map<std::string, unsigned int>::iterator index_to_orders; std::cout << "Iterating orders ..." << std::endl; for(index_to_orders = restaurant_orders.begin(); index_to_orders != restaurant_orders.end(); ++index_to_orders) { std::cout << "\t" << index_to_orders->first << ", order# " << index_to_orders->second << std::endl; } std::map<std::string, unsigned int>::const_iterator index_to_orders_const; std::cout << "Iterating (const) orders ..." << std::endl; for(index_to_orders_const = restaurant_orders.cbegin(); index_to_orders_const != restaurant_orders.cend(); ++index_to_orders_const) { std::cout << "\t" << index_to_orders_const->first << ", order# " << index_to_orders_const->second << std::endl; } std::cout << "Iterating orders with 'next' ..." << std::endl; index_to_orders = restaurant_orders.begin(); while(index_to_orders != restaurant_orders.end()) { std::cout << "\t" << index_to_orders->first << ", order# " << index_to_orders->second << std::endl; index_to_orders = std::next(index_to_orders); } std::map<std::string, unsigned int>::reverse_iterator reverse_index_to_orders; std::cout << "Iterating orders in reverse ..." << std::endl; for(reverse_index_to_orders = restaurant_orders.rbegin(); reverse_index_to_orders != restaurant_orders.rend(); ++reverse_index_to_orders) { std::cout << "\t" << reverse_index_to_orders->first << ", order# " << reverse_index_to_orders->second << std::endl; } std::cout << "Iterating orders with 'prev' ..." << std::endl; index_to_orders = restaurant_orders.end(); do { index_to_orders = std::prev(index_to_orders); std::cout << "\t" << index_to_orders->first << ", order# " << index_to_orders->second << std::endl; } while(index_to_orders != restaurant_orders.begin()); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
27) dynamic_cast |
With dynamic_cast succeeds if the casting is right.
#include <iostream> class human { public: virtual void who_are_you(void) { std::cout << "I am a human" << std::endl; } }; class man : public human { public: void who_are_you(void) { std::cout << "I am a man" << std::endl; } }; class woman : public human { public: void who_are_you(void) { std::cout << "I am a woman" << std::endl; } }; int main() { man unknown_being1; std::cout << "Let us check unknown_being 1 ..." << std::endl; if(human* is_it_human = dynamic_cast<human*>(&unknown_being1)) if(man* is_it_man = dynamic_cast<man*>(&unknown_being1)) std::cout << "Human and Man !" << std::endl; else if(woman* is_it_woman = dynamic_cast<woman*>(&unknown_being1)) std::cout << "Human and Woman !" << std::endl; else std::cout << "Human, but neither Man nor Woman !" << std::endl; else std::cout << "Not human !!" << std::endl; unknown_being1.who_are_you(); woman unknown_being2; std::cout << "Let us check unknown_being 2 ..." << std::endl; if(human* is_it_human = dynamic_cast<human*>(&unknown_being2)) if(man* is_it_man = dynamic_cast<man*>(&unknown_being2)) std::cout << "Human and Man !" << std::endl; else if(woman* is_it_woman = dynamic_cast<woman*>(&unknown_being2)) std::cout << "Human and Woman !" << std::endl; else std::cout << "Human, but neither Man nor Woman !" << std::endl; else std::cout << "Not human !!" << std::endl; unknown_being2.who_are_you(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
28) unique_ptr, shared_ptr, and weak_ptr |
With unique_ptr,
we can ensure to free the memory allocated for objected created with new once it is out of scope.
shared_ptr keep track of pointers to an object, once all pointers are out of scope, object memory is freed. weak_ptr allows access to object pointed by shared_ptr, but it will not block the destruction of the object. If the object need to be accessed via weak_ptr, it can be converted to shared_ptr with lock. shared_ptr and weak_ptr are called smart pointers. #include <iostream> #include <memory> #include <string> class human { private: static unsigned int dna_stamp_ctr; protected: unsigned int dna_stamp; public: human() { dna_stamp = ++dna_stamp_ctr; } }; unsigned int human::dna_stamp_ctr = 0; class man : public human { public: std::string name = "default man"; man() // constructor { std::cout << "[DNA# " << dna_stamp << "] I am born" << std::endl; } ~man() // destructor { std::cout << "[DNA# " << dna_stamp << "] I am dying" << std::endl; } }; int main() { { man* someone_delete_me = new man(); } std::cout << "Did anyone free \"someone_delete_me\" (DNA# 1) ? If no, we have memory leak !" << std::endl; { std::cout << "Inside block1" << std::endl; std::unique_ptr<man> p_temp_worker1(new man()); } std::cout << "Out of block1" << std::endl; { std::cout << "Inside block2" << std::endl; std::shared_ptr<man> pointer_to_temp_worker = nullptr; { std::cout << "Inside block3" << std::endl; std::shared_ptr<man> p_temp_worker2(new man()); pointer_to_temp_worker = p_temp_worker2; } std::cout << "Out of block3" << std::endl; } std::cout << "Out of block2" << std::endl; { std::cout << "Inside block4" << std::endl; std::weak_ptr<man> weak_ptr_to_temp_worker; { std::cout << "Inside block5" << std::endl; std::shared_ptr<man> p_temp_worker3(new man()); weak_ptr_to_temp_worker = p_temp_worker3; if(std::shared_ptr<man> shared_ptr_to_temp_worker = weak_ptr_to_temp_worker.lock()) { std::cout << "I can access temp worker" << std::endl; std::cout << "temp worker's name is " << shared_ptr_to_temp_worker->name << std::endl; } else { std::cout << "I cannot access temp worker" << std::endl; } } std::cout << "Out of block5" << std::endl; if(weak_ptr_to_temp_worker.lock()) { std::cout << "I can access temp worker" << std::endl; } else { std::cout << "I cannot access temp worker" << std::endl; } } std::cout << "Out of block4" << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
29) Creating template for a class |
Say, we need a group, called family and this is to include both men and women.
Below is the implementation using template.
Instead of "typename T", we could say "class T". typename allows template to be used for different type-specifiers like class, struct, and so on. We also used list from standard library. #include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; int main() { man Husband("Husband"); woman Wife("Wife"); group<human> family; family.add_member(Husband); family.add_member(Wife); std::cout << "Family include below members " << std::endl; family.print_group(); woman Mother_in_law("Mom-in-law"); family.add_member(Mother_in_law); std::cout << "Added 1, family now include below members " << std::endl; family.print_group(); family.remove_member(Mother_in_law); std::cout << "Removed 1, family now include below members " << std::endl; family.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
30) Variable template |
Variable template imply variable value based on template.
#include <iostream> class human { private: std::string name; public: human(std::string ip_name) : name(ip_name) {} friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class bird { private: std::string name; public: bird(std::string ip_name) : name(ip_name) {} friend std::ostream& operator<<(std::ostream& out_stream, bird& ip_bird) { out_stream << ip_bird.name; return out_stream; } }; class fish { private: std::string name; public: fish(std::string ip_name) : name(ip_name) {} friend std::ostream& operator<<(std::ostream& out_stream, fish& ip_fish) { out_stream << ip_fish.name; return out_stream; } }; template<typename T> T Super = T("Super"); // variable template int main() { human Superman = Super<human>; std::cout << "This is " << Superman << " human" << std::endl; bird Superbird = Super<bird>; std::cout << "This is " << Superbird << " bird" << std::endl; fish Superfish = Super<fish>; std::cout << "This is " << Superfish << " fish" << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
31) Template specialisation |
Yes, we can provide different template implementation for particular type(s) of template arguments.
#include <iostream> #include <string> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man (std::string ip_name) { name = ip_name; } }; class baby : public human { public: baby (std::string ip_name) { name = ip_name; } }; template<typename T> void speak_up(T& person) { std::cout << "I am " << person << "." << std::endl; } // template specialisation template<> void speak_up<baby>(baby& ip_baby) { std::cout << "I am a baby, I cannot speak." << std::endl; } int main() { baby Cute_baby("Cutie"); man Father("cute baby's father"); speak_up(Cute_baby); speak_up(Father); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
32) Copy versus move, using std::move |
Below example illustrate both Copy and Move.
&& used in operator= overloading is known as "rvalue reference". #include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; std::string name; public: group(const std::string& ip_name) { std::cout << "group(" + ip_name + ")" << std::endl; members = new std::list<T*>; name = ip_name; } ~group() { std::cout << "~group(" + name + ")" << std::endl; delete members; } void add_member(T& person) { members->push_back(&person); } void print_group(void) { if(members == nullptr) { std::cout << "print_group(" + name + "): no members" << std::endl; return; } for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } void operator=(group& ip_group) // copy (lvalue reference) { std::cout << "group operator= with copy()" << std::endl; for(auto member_iterator = ip_group.members->begin(); member_iterator != ip_group.members->end(); ++member_iterator) { T* member = *member_iterator; add_member(*member); } } void operator=(group&& ip_group) // move (rvalue reference) { std::cout << "group operator= with move()" << std::endl; members = ip_group.members; ip_group.members = nullptr; } }; int main() { woman Woman1("Woman1"); woman Woman2("Woman2"); group<woman> cool_kitty_group("cool_kitty_group"); cool_kitty_group.add_member(Woman1); cool_kitty_group.add_member(Woman2); std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "----------" << std::endl; group<woman> awesome_kitty_group("awesome_kitty_group"); awesome_kitty_group = cool_kitty_group; // copy std::cout << "After copy ...." << std::endl; std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "Awesome kitty group include below members " << std::endl; awesome_kitty_group.print_group(); std::cout << "----------" << std::endl; group<woman> renamed_cool_kitty_group("renamed_cool_kitty_group"); renamed_cool_kitty_group = std::move(cool_kitty_group); // move std::cout << "After move ...." << std::endl; std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "If no members are printed, it is ok, as we moved them and *not* copied them ! " << std::endl; std::cout << "Renamed cool kitty group include below members " << std::endl; renamed_cool_kitty_group.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
33) copy constructor and move constructor |
Copy and move in context of construction.
#include <iostream> #include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; std::string name; public: group(const std::string& ip_name) { std::cout << "group(" + ip_name + ")" << std::endl; members = new std::list<T*>; name = ip_name; } group(group& ip_group) // copy constructor { std::cout << "group() copy" << std::endl; members = new std::list<T*>; name = ip_group.name; for(auto member_iterator = ip_group.members->begin(); member_iterator != ip_group.members->end(); ++member_iterator) { T* member = *member_iterator; add_member(*member); } } group(group&& ip_group) // move constructor { std::cout << "group() move" << std::endl; members = ip_group.members; ip_group.members = nullptr; name = ip_group.name; } ~group() { std::cout << "~group(" + name + ")" << std::endl; delete members; } void add_member(T& person) { members->push_back(&person); } void print_group(void) { if(members == nullptr) { std::cout << "print_group(" + name + "): no members" << std::endl; return; } for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } void operator=(group& ip_group) // copy (lvalue reference) { std::cout << "group operator= with copy()" << std::endl; for(auto member_iterator = ip_group.members->begin(); member_iterator != ip_group.members->end(); ++member_iterator) { T* member = *member_iterator; add_member(*member); } } void operator=(group&& ip_group) // move (rvalue reference) { std::cout << "group operator= with move()" << std::endl; members = ip_group.members; ip_group.members = nullptr; } }; int main() { woman Woman1("Woman1"); woman Woman2("Woman2"); group<woman> cool_kitty_group("cool_kitty_group"); cool_kitty_group.add_member(Woman1); cool_kitty_group.add_member(Woman2); std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "----------" << std::endl; group<woman> cool_kitty_group2(cool_kitty_group); // copy constructor std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "Cool kitty group2 include below members " << std::endl; cool_kitty_group2.print_group(); std::cout << "----------" << std::endl; group<woman> renamed_cool_kitty_group(std::move(cool_kitty_group)); // move constructor std::cout << "Cool kitty group include below members " << std::endl; cool_kitty_group.print_group(); std::cout << "If no members are printed, it is ok, as we moved them and *not* copied them ! " << std::endl; std::cout << "Renamed cool kitty group include below members " << std::endl; renamed_cool_kitty_group.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
34) move_iterator |
Below example has poor design, done this way only to showcase move_iterator.
Good design will ensure moving/copying in much better way (ref: std::move example). #include <iostream> #include <string> #include <vector> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; int main() { man Man1("Man1"); man Man2("Man2"); std::vector<man> mens_cricket_team_state_karnataka = {Man1, Man2}; std::cout << "Men's team (Karanataka)..." << std::endl; for(man& team_member:mens_cricket_team_state_karnataka) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; man Man3("Man3"); man Man4("Man4"); std::vector<man> mens_cricket_team_state_maharashtra = {Man3, Man4}; std::cout << "Men's team (Maharashtra)..." << std::endl; for(man& team_member:mens_cricket_team_state_maharashtra) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; // copy std::vector<man> mens_cricket_team_ipl; std::copy(mens_cricket_team_state_karnataka.begin(), mens_cricket_team_state_karnataka.end(), std::back_inserter(mens_cricket_team_ipl)); std::copy(mens_cricket_team_state_maharashtra.begin(), mens_cricket_team_state_maharashtra.end(), std::back_inserter(mens_cricket_team_ipl)); std::cout << "State teams are copied to IPL team" << std::endl; std::cout << "Men's IPL team..." << std::endl; for(man& team_member:mens_cricket_team_ipl) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; std::cout << "Men's team (Karanataka)..." << std::endl; for(man& team_member:mens_cricket_team_state_karnataka) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; std::cout << "Men's team (Maharashtra)..." << std::endl; for(man& team_member:mens_cricket_team_state_maharashtra) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; // move with move_iterator std::vector<man> mens_cricket_team_india; // in C++11, you will have to use make_move_iterator instead of move_iterator // in C++17, due to template argument deduction, this work ok std::move(std::move_iterator(mens_cricket_team_ipl.begin()), std::move_iterator(mens_cricket_team_ipl.end()), std::back_inserter(mens_cricket_team_india)); // mens_cricket_team_ipl must *not* be used std::cout << "IPL team has been moved to India team" << std::endl; std::cout << "Men's India team..." << std::endl; for(man& team_member:mens_cricket_team_india) std::cout << team_member << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
35) try and catch |
Below is a simple example of error handling.
#include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } class error_no_list { public: std::string error_text = "group error: list not present"; }; void print_group(void) { if(members == nullptr) throw error_no_list(); for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } void operator=(group&& ip_group) // move { members = ip_group.members; ip_group.members = nullptr; } }; int main() { group<human> employess_on_bench; man Employee1("Employee1"); woman Employee2("Employee2"); employess_on_bench.add_member(Employee1); employess_on_bench.add_member(Employee2); std::cout << "Employees on bench are:" << std::endl; try { employess_on_bench.print_group(); } catch(group<human>::error_no_list) { std::cout << "Error: print_group failed: '" << group<human>::error_no_list().error_text << "'" << std::endl; return 1; } catch(...) // all other errors { std::cout << "Unhandled error." << std::endl; return 2; } group<human> employess_on_work; employess_on_work = std::move(employess_on_bench); std::cout << "Employees on bench are:" << std::endl; try { employess_on_bench.print_group(); } catch(group<human>::error_no_list) { std::cout << "Error: print_group failed: '" << group<human>::error_no_list().error_text << "'" << std::endl; return 1; } catch(...) // all other errors { std::cout << "Unhandled error." << std::endl; return 2; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
36) noexcept |
If the function is noexcept, it is assumed that it will never throw an exception.
If it does, the program may be terminated. noexcept operator allows us to make it conditional. In below examples, count_population is *not* supposed to throw any exception if it is called for human. So it works ok in first case, but program terminates in second case. For bird, count_population may throw an error, so the exceptions are handled gracefully (it does *not* terminate the program). count_population<human> work ok #include <iostream> #include <regex> class human { public: const static bool traceable = true; }; class bird { public: const static bool traceable = false; }; template<typename T> bool constexpr is_traceable(void) { return T::traceable; } class counting_exception {}; template<typename T> void count_population(void) noexcept(is_traceable<T>()) { // if T is human, I can count with noexceptions // if T is bird, I may throw exceptions std::regex human_pattern {"human"}; std::regex bird_pattern {"bird"}; if(std::regex_search(typeid(T{}).name(), human_pattern)) { //..... std::cout << "Counted humans, all good !" << std::endl; } else if(std::regex_search(typeid(T{}).name(), bird_pattern)) { std::cout << "Counting birds, may face issues !" << std::endl; //...... throw counting_exception(); } } int main() { if(is_traceable<human>()) std::cout << "Humans are traceable !" << std::endl; else std::cout << "Humans are *not* traceable !" << std::endl; if(is_traceable<bird>()) std::cout << "Birds are traceable !" << std::endl; else std::cout << "Birds are *not* traceable !" << std::endl; try { count_population<bird>(); } catch(...) { std::cout << "Counting error in birds !" << std::endl; } try { count_population<human>(); } catch(...) { std::cout << "Counting error in humans !" << std::endl; } std::cout << "----------" << std::endl; return 0; }
count_population<human> throw exception #include <iostream> #include <regex> class human { public: const static bool traceable = true; }; class bird { public: const static bool traceable = false; }; template<typename T> bool constexpr is_traceable(void) { return T::traceable; } class counting_exception {}; template<typename T> void count_population(void) noexcept(is_traceable<T>()) { // if T is human, I can count with noexceptions // if T is bird, I may throw exceptions std::regex human_pattern {"human"}; std::regex bird_pattern {"bird"}; if(std::regex_search(typeid(T{}).name(), human_pattern)) { //..... std::cout << "Counting humans, something went wrong" << std::endl; throw counting_exception(); } else if(std::regex_search(typeid(T{}).name(), bird_pattern)) { //...... std::cout << "Counted birds, all good !" << std::endl; } } int main() { if(is_traceable<human>()) std::cout << "Humans are traceable !" << std::endl; else std::cout << "Humans are *not* traceable !" << std::endl; if(is_traceable<bird>()) std::cout << "Birds are traceable !" << std::endl; else std::cout << "Birds are *not* traceable !" << std::endl; try { count_population<bird>(); } catch(...) { std::cout << "Counting error in birds !" << std::endl; } try { count_population<human>(); } catch(...) { std::cout << "Counting error in humans !" << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
37) enum class |
Yes, pure (as found in C) enum and enum class are different.
#include <iostream> #include <string> #include <vector> class human { protected: std::string name; enum class gender_t { male, female } gender; public: virtual std::string get_name(void) = 0; virtual std::string get_gender(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; gender = gender_t::male; } std::string get_name(void) { return name; } std::string get_gender(void) { return "male"; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; gender = gender_t::female; } std::string get_name(void) { return name; } std::string get_gender(void) { return "female"; } }; int main() { const unsigned int number_of_employees = 2; std::vector<human*> employees(number_of_employees); man Employee1("Employee1"); woman Employee2("Employee2"); employees[0] = &Employee1; employees[1] = &Employee2; std::cout << "Name\tGender" << std::endl; for(auto index = 0; index < number_of_employees; ++index) { std::cout << employees[index]->get_name() << "\t" << employees[index]->get_gender() << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
38) Example of const, aka "immutable" |
First program is without const and second one is with const.
Without const #include <iostream> #include <string> class human { protected: std::string* name; }; class man : public human { public: man(std::string ip_name) { name = new std::string(); *name = ip_name; } ~man() { delete name; } std::string* get_name(void) { return name; } }; int main() { man Man1("Man1"); std::cout << "Man1's name is '" << *(Man1.get_name()) << "'" << std::endl; std::string* hack_name = Man1.get_name(); *hack_name = "Man2"; std::cout << "Man1's name is '" << *(Man1.get_name()) << "'" << std::endl; std::cout << "----------" << std::endl; return 0; }
With const #include <iostream> #include <string> class human { protected: std::string* name; }; class man : public human { public: man(std::string ip_name) { name = new std::string(); *name = ip_name; } ~man() { delete name; } const std::string* get_name(void) { return name; } }; int main() { man Man1("Man1"); std::cout << "Man1's name is '" << *(Man1.get_name()) << "'" << std::endl; std::string* hack_name = Man1.get_name(); *hack_name = "Man2"; std::cout << "Man1's name is '" << *(Man1.get_name()) << "'" << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
39) constexpr |
constexpr is more like C macro i.e. it is evaluated at compile time.
For example, constexpr double PI = ((double)22)/7;. constexpr could be a function (including constructors). If possible, it is evaluated at compile time, otherwise at runtime. #include <iostream> constexpr double get_me_PI(void) { return ((double)22)/7; // compile time } constexpr double get_me_area_of_circle(unsigned int radius) { return get_me_PI()*radius*radius; // runtime } int main() { unsigned int circle1_radius; std::cout << "Enter radius: "; std::cin >> circle1_radius; std::cout << "Area of circle with radius " << circle1_radius; std::cout << " unit is " << get_me_area_of_circle(circle1_radius) << " unit-square." << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
40) Compile time if (C++ 17) |
Below is an example of using constexpr in if statement.
#include <iostream> namespace rel1 { void basic_processing(void) { std::cout << "basic_processing()" << std::endl; } } namespace rel2 { const bool enabled = true; void enhanced_processing(void) { std::cout << "enhanced_processing()" << std::endl; } } namespace rel3 { const bool enabled = false; void ultra_enhanced_processing(void) { std::cout << "ultra_enhanced_processing()" << std::endl; } } int main() { using namespace rel1; if constexpr(rel3::enabled) { rel3::ultra_enhanced_processing(); } else if constexpr(rel2::enabled) { rel2::enhanced_processing(); } else { basic_processing(); } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
41) override |
If override is added to a member function,
it makes it explicit to compiler that derived class is overriding the parent class function.
In below example, mute_check has been misspelled in class mute_person. Without override, program compiles, but gives incorrect result. With override, the error is caught. Without override #include <iostream> #include <string> #include <vector> class person { protected: std::string name; public: person(std::string ip_name) : name(ip_name) {} virtual bool mute_check(void) { return false; } friend std::ostream& operator<<(std::ostream& out_stream, person& ip_person) { out_stream << ip_person.name; return out_stream; } }; class mute_person : public person { public: mute_person(std::string ip_name) : person(ip_name) {} bool mute_chuck(void) { return true; } }; int main() { person Person1("Person1"); mute_person Person2("Person2"); std::vector<person*> people{&Person1, &Person2}; for(auto p : people) { if(p->mute_check()) std::cout << *p << " is mute" << std::endl; else std::cout << *p << " is *not* mute" << std::endl; } std::cout << "----------" << std::endl; return 0; }
With override #include <iostream> #include <string> #include <vector> class person { protected: std::string name; public: person(std::string ip_name) : name(ip_name) {} virtual bool mute_check(void) { return false; } friend std::ostream& operator<<(std::ostream& out_stream, person& ip_person) { out_stream << ip_person.name; return out_stream; } }; class mute_person : public person { public: mute_person(std::string ip_name) : person(ip_name) {} bool mute_chuck(void) override { return true; } }; int main() { person Person1("Person1"); mute_person Person2("Person2"); std::vector<person*> people{&Person1, &Person2}; for(auto p : people) { if(p->mute_check()) std::cout << *p << " is mute" << std::endl; else std::cout << *p << " is *not* mute" << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
42) Restricting function override with final |
final ensures that function is not overloaded (by derived classes).
#include <iostream> #include <string> class human { protected: std::string name; public: virtual std::string get_name(void) final { return name; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Man1("Man1") std::cout << Man1 << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
43) Traversing vector |
Though example is for vector, it is applicable to list and other sequences.
#include <iostream> #include <string> #include <vector> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { const unsigned int number_of_employees = 2; std::vector<human*> employees(number_of_employees); man Employee1("Employee1"); woman Employee2("Employee2"); employees[0] = &Employee1; employees[1] = &Employee2; std::cout << "Traversing employees ..." << std::endl; for(auto employee : employees) //vector supports begin() and end() { std::cout << *employee << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
44) Function object aka functor |
Like objects, Function objects have their own variables and
multiple copies can be created of it.
Function object is often called callable object. You may compare it (in limited way) with static variables inside a function in C. #include <iostream> #include <string> #include <vector> class human { protected: std::string name; unsigned int height; // in centimeter unsigned int weight; // in grams public: virtual std::string get_name(void) final { return name; } virtual unsigned int get_height(void) final { return height; } virtual unsigned int get_weight(void) final { return weight; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name, unsigned short ip_height, unsigned int ip_weight) { name = ip_name; height = ip_height; weight = ip_weight; } }; class ref_measurement { private: unsigned int ref_val; public: ref_measurement(unsigned int ip_val):ref_val(ip_val) {} bool operator()(unsigned int compare_val) { return compare_val > ref_val; } }; constexpr unsigned int average_height = 167; ref_measurement more_than_ref_height(average_height); constexpr unsigned int average_weight = 60000; ref_measurement more_than_ref_weight(average_weight); int main() { man Man1("Man1", 183, 80000); man Boy1("Boy1", 152, 65000); std::vector<human*> candidates(2); candidates[0] = &Man1; candidates[1] = &Boy1; for(auto p_candidate : candidates) { std::cout << "----------" << std::endl; if(more_than_ref_height(p_candidate->get_height())) std::cout << *p_candidate << " has above average height" << std::endl; else std::cout << *p_candidate << " has below average height" << std::endl; if(more_than_ref_weight(p_candidate->get_weight())) std::cout << *p_candidate << " has above average weight" << std::endl; else std::cout << *p_candidate << " has below average weight" << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
45) Functor with template |
template will allow further generic implementation.
#include <iostream> #include <string> #include <vector> class human { protected: std::string name; unsigned int height; // in centimeter unsigned int weight; // in grams public: virtual std::string get_name(void) final { return name; } virtual unsigned int get_height(void) final { return height; } virtual unsigned int get_weight(void) final { return weight; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name, unsigned short ip_height, unsigned int ip_weight) { name = ip_name; height = ip_height; weight = ip_weight; } }; template<typename T> class ref_measurement { private: const T ref_val; public: ref_measurement(const T& ip_val):ref_val(ip_val) {} bool operator()(const T& compare_val) const { return compare_val > ref_val; } }; constexpr unsigned int average_height = 167; ref_measurement<unsigned int> more_than_ref_height(average_height); constexpr unsigned int average_weight = 60000; ref_measurement<unsigned int> more_than_ref_weight(average_weight); int main() { man Man1("Man1", 183, 80000); man Boy1("Boy1", 152, 65000); std::vector<human*> candidates(2); candidates[0] = &Man1; candidates[1] = &Boy1; for(auto p_candidate : candidates) { std::cout << "----------" << std::endl; if(more_than_ref_height(p_candidate->get_height())) std::cout << *p_candidate << " has above average height" << std::endl; else std::cout << *p_candidate << " has below average height" << std::endl; if(more_than_ref_weight(p_candidate->get_weight())) std::cout << *p_candidate << " has above average weight" << std::endl; else std::cout << *p_candidate << " has below average weight" << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
46) Generic Lambda expression (C++14) |
"[](){}" is a Lambda expression.
It is basically function on the fly.
It stands for "[captured variables](input parameters){implementation}". In below example, more_than_ref_height and more_than_ref_weight do very similar processing, but former uses input parameters and latter uses captured variables. #include <iostream> #include <string> #include <vector> class human { protected: std::string name; unsigned int height; // in centimeter unsigned int weight; // in grams public: virtual std::string get_name(void) final { return name; } virtual unsigned int get_height(void) final { return height; } virtual unsigned int get_weight(void) final { return weight; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name, unsigned short ip_height, unsigned int ip_weight) { name = ip_name; height = ip_height; weight = ip_weight; } }; constexpr unsigned int average_height = 167; constexpr unsigned int average_weight = 60000; auto more_than_ref_height = [](unsigned int l_height){ return (l_height > average_height); }; int main() { man Man1("Man1", 183, 80000); man Boy1("Boy1", 152, 65000); std::vector<human*> candidates(2); candidates[0] = &Man1; candidates[1] = &Boy1; for(auto p_candidate : candidates) { std::cout << "----------" << std::endl; if(more_than_ref_height(p_candidate->get_height())) std::cout << *p_candidate << " has above average height" << std::endl; else std::cout << *p_candidate << " has below average height" << std::endl; auto more_than_ref_weight = [p_candidate](){ return (p_candidate->get_weight() > average_weight); }; if(more_than_ref_weight()) std::cout << *p_candidate << " has above average weight" << std::endl; else std::cout << *p_candidate << " has below average weight" << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
47) Using Generic Lambda effectively (C++14) |
Clever use of auto and Lambda's capture capability indeed.
#include <iostream> auto table_of_x = [](int x) { auto multiply = [x](int n) { return x * n; }; return multiply; }; int main() { const int counters[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto table_of_2 = table_of_x(2); auto table_of_3 = table_of_x(3); for (auto n:counters) std::cout << "2 * " << n << " = " << table_of_2(n) << std::endl; std::cout << "----------" << std::endl; for (auto n:counters) std::cout << "3 * " << n << " = " << table_of_3(n) << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
48) static_assert |
static_assert is basically to do a compile time check.
Below example also uses utility called is_assignable from standard library. #include <iostream> template<typename T1, typename T2> void compile_time_compatibility_check() { static_assert(std::is_assignable<T1, T2>::value, "Not compatible"); }; class human { }; class man : public human { }; class alien { }; int main() { compile_time_compatibility_check<human, man>(); // OK compile_time_compatibility_check<man, human>(); // assertion 'Not compatible' compile_time_compatibility_check<alien, human>(); // assertion 'Not compatible' return 0; }
| ||||||||||||||||||||||||
49) Aliases, keyword using |
using keyword allows us to create aliases (synonyms) for types and templates.
#include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; // aliases using mens_group = group<man>; using womens_group = group<woman>; int main() { mens_group mens_soccer_team; man Man1("Man1"); man Man2("Man2"); mens_soccer_team.add_member(Man1); mens_soccer_team.add_member(Man2); std::cout << "Soccer team:" << std::endl; mens_soccer_team.print_group(); womens_group kitty_group; woman Woman1("Woman1"); woman Woman2("Woman2"); kitty_group.add_member(Woman1); kitty_group.add_member(Woman2); std::cout << "Kitty group:" << std::endl; kitty_group.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
50) Duck typing |
Though we had created group template for human,
alien also was able to use the template.
This was possible as alien behaved same way as human ! This is known as Duck typing (looks like duck, quacks like duck, so it is a duck). #include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; class alien { private: std::string name; public: alien(std::string ip_name) { name = ip_name; } friend std::ostream& operator<<(std::ostream& out_stream, alien& ip_alien) { out_stream << ip_alien.name; return out_stream; } }; int main() { group<alien> secret_group; alien Neila1("Neila1"), Neila2("Neila2"); secret_group.add_member(Neila1); secret_group.add_member(Neila2); std::cout << "Secret group has below members:" << std::endl; secret_group.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
51) concept and requires (C++20) |
To ensure that duck typing do not happen, we need certain compile time checks.
This is possible with C++20 requires. Without concept #include <iostream> #include <string> #include <list> class human { private: static unsigned int human_dna_ctr; protected: std::string name; static unsigned int human_dna; public: human() { human_dna = ++human_dna_ctr; } virtual unsigned int get_human_dna(void) final { return human_dna; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } }; template<typename T> requires requires(T a) { a.get_human_dna(); } class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; class alien { private: std::string name; public: alien(std::string ip_name) { name = ip_name; } friend std::ostream& operator<<(std::ostream& out_stream, alien& ip_alien) { out_stream << ip_alien.name; return out_stream; } }; int main() { group<man> mens_soccer_team; group<alien> secret_group; std::cout << "----------" << std::endl; return 0; }
With concept #include <iostream> #include <string> #include <list> class human { private: static unsigned int human_dna_ctr; protected: std::string name; static unsigned int human_dna; public: human() { human_dna = ++human_dna_ctr; } virtual unsigned int get_human_dna(void) final { return human_dna; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } }; template <typename T> concept requires_human_dna = requires(T a) { a.get_human_dna(); }; template<typename T> requires requires_human_dna<T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; class alien { private: std::string name; public: alien(std::string ip_name) { name = ip_name; } friend std::ostream& operator<<(std::ostream& out_stream, alien& ip_alien) { out_stream << ip_alien.name; return out_stream; } }; int main() { group<man> mens_soccer_team; group<alien> secret_group; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
52) Variadic templates |
template with variable number of arguments is a Variadic template.
Below is a recursive implementation of one such template. #include <iostream> #include <string> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } virtual bool above_18(void) = 0; }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return false; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; bool pub_entry_allowed() { return true; } template<typename T, typename... Rest> bool pub_entry_allowed(T& first, Rest&... rest) { if(first.above_18()) return pub_entry_allowed(rest...); // recursive ! else return false; }; int main() { man Man1("Man1"); woman Woman1("Woman1"); boy Boy1("Boy1"); bool allowed_or_not = pub_entry_allowed(Man1, Boy1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Boy1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Boy1, Woman1}." << std::endl; allowed_or_not = pub_entry_allowed(Man1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Woman1}." << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
53) Fold expression, "+...+" (C++17) |
Fold expression is created to simplify implementation of variadic templates.
#include <iostream> #include <string> #include <list> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { std::cout << "group()" << std::endl; members = new std::list<T*>; } ~group() { std::cout << "~group()" << std::endl; delete members; } void add_member(T& person) { members->push_back(&person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; group<human>& operator+(group<human>& ip_group, human& Human1) { std::cout << "group operator+(): " << Human1 << std::endl; ip_group.add_member(Human1); return ip_group; } template<typename... T> void form_group(group<human>& ip_group, T&... people) // alternatively, group<human>& form_group(group<human>& ip_group, T&... people) { std::cout << "form_group()" << std::endl; (ip_group +...+ people); // fold expression // alternatively, return (ip_group +...+ people); } int main() { man Man1("Man1"); woman Woman1("Woman1"); boy Boy1("Boy1"); group<human> general_group; form_group(general_group, Man1, Woman1, Boy1); // alternatively, general_group = form_group(general_group, Man1, Woman1, Boy1); std::cout << "----------" << std::endl; std::cout << "general_group has below below members " << std::endl; general_group.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
54) Using std::forward |
When arguments are passed from function to function, intermediate functions should be able transparently forward the arguments,
we may call it "perfect forwarding".
std::move will work too, however intermediate functions will have to know the types and number of arguments. #include <iostream> #include <string> class SRB_Channel_Configuration { public: int srb_cfg_parameter; SRB_Channel_Configuration() { std::cout << "SRB_Channel_Configuration()" << std::endl; } SRB_Channel_Configuration(int ip_srb_cfg_parameter) : srb_cfg_parameter(ip_srb_cfg_parameter) { std::cout << "SRB_Channel_Configuration()" << std::endl; } ~SRB_Channel_Configuration() { std::cout << "~SRB_Channel_Configuration()" << std::endl; } }; class UE_Channel_SRB { public: int srb_cfg; int additional_config; void Configure(SRB_Channel_Configuration& configuration, int ip_additional_config) { std::cout << "UE_Channel_SRB.Configure(lvalue reference)" << std::endl; srb_cfg = configuration.srb_cfg_parameter; additional_config = ip_additional_config; } void Configure(SRB_Channel_Configuration&& configuration, int ip_additional_config) { std::cout << "UE_Channel_SRB.Configure(rvalue reference)" << std::endl; srb_cfg = configuration.srb_cfg_parameter; additional_config = ip_additional_config; } void print_configuration(void) { std::cout << "srb_cfg = " << srb_cfg << std::endl; std::cout << "additional_config = " << additional_config << std::endl; } }; enum class srb_channel_type { SRB0, SRB1 }; class UE_MAC { public: UE_Channel_SRB srb0_channel; UE_Channel_SRB srb1_channel; template<typename... T> void SRB_Configure(srb_channel_type ch_type, T&&... Configuration) { switch(ch_type) { case srb_channel_type::SRB0: srb0_channel.Configure(std::forward<T>(Configuration)...); break; case srb_channel_type::SRB1: srb1_channel.Configure(std::forward<T>(Configuration)...); break; default: break; } } void print_configuration(void) { std::cout << "SRB0 configuration:" << std::endl; srb0_channel.print_configuration(); std::cout << "SRB1 configuration:" << std::endl; srb1_channel.print_configuration(); } }; class UE_RRC { public: UE_MAC* p_ue_mac = nullptr; UE_RRC(UE_MAC* const ip_p_ue_mac) : p_ue_mac(ip_p_ue_mac) {} template<typename... T> void SRB_Configure(srb_channel_type ch_type, T&&... Configuration) { p_ue_mac->SRB_Configure(ch_type, std::forward<T>(Configuration)...); } }; int main() { UE_MAC ue_mac; UE_RRC ue_rrc(&ue_mac); ue_rrc.SRB_Configure(srb_channel_type::SRB0, SRB_Channel_Configuration{0}, 123); std::cout << "----------" << std::endl; SRB_Channel_Configuration srb1_configuration{1}; ue_rrc.SRB_Configure(srb_channel_type::SRB1, srb1_configuration, 456); std::cout << "----------" << std::endl; ue_mac.print_configuration(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
55) Critical suffix "&" (reference to) |
Please refer one of earlier examples rewritten with and without "&" in function pub_entry_allowed.
With "&" #include <iostream> #include <string> class human { protected: std::string name; public: human() { std::cout << "Human born" << std::endl; } human(human&) // copy constructor { std::cout << "Human copied" << std::endl; } ~human() { std::cout << "Human died" << std::endl; } virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } virtual bool above_18(void) = 0; }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return false; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; bool pub_entry_allowed() { return true; } template<typename T, typename... Rest> bool pub_entry_allowed(T& first, Rest&... rest) { if(first.above_18()) return pub_entry_allowed(rest...); // recursive ! else return false; }; int main() { man Man1("Man1"); woman Woman1("Woman1"); boy Boy1("Boy1"); bool allowed_or_not = pub_entry_allowed(Man1, Boy1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Boy1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Boy1, Woman1}." << std::endl; allowed_or_not = pub_entry_allowed(Man1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Woman1}." << std::endl; std::cout << "----------" << std::endl; return 0; }
There are equal (3) of "born and "died", so copy was avoided. If we do not use "&", we end up in multiple copy operations as seen below. Without "&" #include <iostream> #include <string> class human { protected: std::string name; public: human() { std::cout << "Human born" << std::endl; } human(human&) // copy constructor { std::cout << "Human copied" << std::endl; } ~human() { std::cout << "Human died" << std::endl; } virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } virtual bool above_18(void) = 0; }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return false; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } bool above_18(void) { return true; } }; bool pub_entry_allowed() { return true; } template<typename T, typename... Rest> bool pub_entry_allowed(T first, Rest... rest) { if(first.above_18()) return pub_entry_allowed(rest...); // recursive ! else return false; }; int main() { man Man1("Man1"); woman Woman1("Woman1"); boy Boy1("Boy1"); bool allowed_or_not = pub_entry_allowed(Man1, Boy1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Boy1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Boy1, Woman1}." << std::endl; allowed_or_not = pub_entry_allowed(Man1, Woman1); if(allowed_or_not) std::cout << "Entry into pub allowed for group {Man1, Woman1}." << std::endl; else std::cout << "Entry into pub *not* allowed for group {Man1, Woman1}." << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
56) RAII |
Technique of acquiring all resources (say allocation of memory) in constructor (of the classes) and
releasing it (freeing memory) in destructor (of corresponding classes) is known as Resource Acquisition Is Initialisation (RAII). Practically, it means that we do not acquire/release resources in general code, but rather keep it embedded within an appropriate class. This will also mean that we avoid "naked" new/delete and keep them as part of some other constructor and destructor respectively. If the resource in the question is memory, above technique will avoid memory leak. | ||||||||||||||||||||||||
57) Policy based design |
Certain parts of implementation may need to be kept flexible based on some aspects of the platform
(e.g. processing speed, memory, type of peripherals, input/output devices, and so on). In our earlier example of "Creating template for a class", for implementing group, we used list from standard library. We could have used vector instead. Separating out such details in smaller classes is called "Policy based design" and these classes are known as policies. Below example shows two policies for storing the members, one with vector, and another with list. #include <iostream> #include <string> #include <vector> #include <list> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } }; class girl : public woman { public: girl(std::string ip_name) { name = ip_name; } }; //Policy#1 - Store in vector template<typename T> struct vector_store { using item_type = T; std::vector<item_type*> storage; void add_item(item_type* item) { storage.push_back(item); } void print_items(void) { for(auto* item:storage) std::cout << *item << std::endl; } }; //Policy#2 - Store in list template<typename T> struct list_store { using item_type = T; std::list<item_type*> storage; void add_item(item_type* item) { storage.push_back(item); } void print_items(void) { for(auto* item:storage) std::cout << *item << std::endl; } }; template<typename store_policy> class group { private: store_policy store; public: void add_member(store_policy::item_type& item) { store.add_item(&item); } void print_group(void) { store.print_items(); } }; int main() { man Father("Father"); woman Mother("Mother"); boy Son("Son"); girl Daughter("Daughter"); group<vector_store<human>> vectorFamily; vectorFamily.add_member(Father); vectorFamily.add_member(Mother); vectorFamily.add_member(Son); vectorFamily.add_member(Daughter); std::cout << "vectorFamily includes .... " << std::endl; vectorFamily.print_group(); group<list_store<human>> listFamily; listFamily.add_member(Father); listFamily.add_member(Mother); listFamily.add_member(Son); listFamily.add_member(Daughter); std::cout << "listFamily includes .... " << std::endl; listFamily.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
58) Type checking with concept and requires |
In an example for Policy based design, both policies vector_store,
and list_store will work only if type T supports operator <<.
Instead of run-time failure, we can do this check at compile time with concept and requires. #include <iostream> #include <string> #include <vector> #include <list> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } }; class girl : public woman { public: girl(std::string ip_name) { name = ip_name; } }; template <typename T> concept supports_cout = requires(T a) { std::cout << a; }; //Policy#1 - Store in vector template<typename T> requires supports_cout<T> struct vector_store { using item_type = T; std::vector<T*> storage; void add_item(T* item) { storage.push_back(item); } void print_items(void) { for(auto* item:storage) std::cout << *item << std::endl; } }; //Policy#2 - Store in list template<typename T> requires supports_cout<T> struct list_store { using item_type = T; std::list<T*> storage; void add_item(T* item) { storage.push_back(item); } void print_items(void) { for(auto* item:storage) std::cout << *item << std::endl; } }; template<typename store_policy> class group { private: store_policy store; public: void add_member(store_policy::item_type& item) { store.add_item(&item); } void print_group(void) { store.print_items(); } }; class mannequin { }; int main() { group<vector_store<human>> vectorFamily; group<list_store<human>> listFamily; group(vector_store<mannequin>> Dummies; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
59) Containers from standard library |
As of C++17,
For more details on each, you may refer en.cppreference.com, cplusplus.com or devdocs.io. | ||||||||||||||||||||||||
60) Predicate (with find_if) |
Let us say, we want to iterate through a container, looking for the object that satisfy particular condition.
This is possible with standard library algorithm find_if and such condition is known as a Predicate. #include <iostream> #include <string> #include <vector> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { std::vector<human*> mens_soccer_team; man Man1("Man1"); man Man2("Man2"); man Man3("Man3"); man Man4("Man4"); man Man5("Man5"); mens_soccer_team.push_back(&Man1); mens_soccer_team.push_back(&Man2); mens_soccer_team.push_back(&Man3); mens_soccer_team.push_back(&Man4); mens_soccer_team.push_back(&Man5); std::string looking_for = "Man4"; // Predicate with lamda expression auto find_man_lamda = std::find_if(mens_soccer_team.begin(), mens_soccer_team.end(), [looking_for](human* man_in_team) { if(man_in_team->get_name() == looking_for) return true; return false; } ); if(*find_man_lamda == nullptr) std::cout << "Lamda: Man4 *not* found." << std::endl; else std::cout << "Lamda: " << **find_man_lamda << " found." << std::endl; class find_if_man_functor { private: std::string find_name; public: find_if_man_functor(const std::string& ip_find_name) : find_name(ip_find_name) {} bool operator()(human* man_in_team) const { if(man_in_team->get_name() == find_name) return true; return false; } }; // Predicate with functor auto find_man_functor = std::find_if(mens_soccer_team.begin(), mens_soccer_team.end(), find_if_man_functor(looking_for)); if(*find_man_functor == nullptr) std::cout << "Functor: Man4 *not* found." << std::endl; else std::cout << "Functor: " << **find_man_functor << " found." << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
61) Using random from standard library |
Below is random number generator template based on newer "Minimum standard" random engine.
We used newer Minimal standard random generator (minstd_rand), others are mt19937, knuth_b, ranlux24_base, and so on. #include <iostream> #include <random> template <class T> class simple_integer_random_generator { private: unsigned int range_from; unsigned int range_to; T* p_generator; std::uniform_int_distribution<int>* p_distribution; public: simple_integer_random_generator(unsigned int ip_range_from, unsigned int ip_range_to) : range_from(ip_range_from), range_to(ip_range_to) { /* no checks for range_from and range_to */ std::random_device rd; //Seed p_generator = new T(rd()); p_distribution = new std::uniform_int_distribution<int>(range_from, range_to); } ~simple_integer_random_generator() { delete p_distribution; delete p_generator; } unsigned int get_rand(void) { return (*p_distribution)(*p_generator); } }; int main() { simple_integer_random_generator<std::minstd_rand> random_engine(1,100); unsigned int random_int; unsigned int how_many = 10; do { random_int = random_engine.get_rand(); std::cout << random_int << std::endl; --how_many; } while (how_many); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
62) Alternatives example with variant |
Is it possible that function returns different types ?
If yes, you may use variant.
#include <iostream> #include <string> #include <variant> #include <random> class human { private: std::string name; public: human(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~human() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class bird { private: std::string name; public: bird(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~bird() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, bird& ip_bird) { out_stream << ip_bird.name; return out_stream; } }; class fish { private: std::string name; public: fish(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~fish() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, fish& ip_fish) { out_stream << ip_fish.name; return out_stream; } }; template <class T> class simple_integer_random_generator { private: unsigned int range_from; unsigned int range_to; T* p_generator; std::uniform_int_distribution<int>* p_distribution; public: simple_integer_random_generator(unsigned int ip_range_from, unsigned int ip_range_to) : range_from(ip_range_from), range_to(ip_range_to) { /* no checks for range_from and range_to */ std::random_device rd; //Seed p_generator = new T(rd()); p_distribution = new std::uniform_int_distribution<int>(range_from, range_to); } ~simple_integer_random_generator() { delete p_distribution; delete p_generator; } unsigned int get_rand(void) { return (*p_distribution)(*p_generator); } }; std::variant<human*, bird*, fish*> nature_factory(void) { static simple_integer_random_generator<std::minstd_rand> random_engine(1,3); switch(random_engine.get_rand()) { case 1: return new human("Human");break; case 2: return new bird("Bird");break; case 3: default: break; } return new fish("Fish"); } int main() { auto p_being = nature_factory(); if(std::holds_alternative<human*>(p_being)) { std::cout << "It is a " << *(std::get<human*>(p_being)) << std::endl; } else if(std::holds_alternative<bird*>(p_being)) { std::cout << "It is a " << *(std::get<bird*>(p_being)) << std::endl; } else if(std::holds_alternative<fish*>(p_being)) { std::cout << "It is a " << *(std::get<fish*>(p_being)) << std::endl; } // ensure to delete if(std::holds_alternative<human*>(p_being)) { delete (std::get<human*>(p_being)); } else if(std::holds_alternative<bird*>(p_being)) { delete (std::get<bird*>(p_being)); } else if(std::holds_alternative<fish*>(p_being)) { delete (std::get<fish*>(p_being)); } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
63) Alternatives example with any |
Below example illustrate any and regular expressions
#include <iostream> #include <string> #include <any> #include <random> #include <regex> class human { private: std::string name; public: human(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~human() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class bird { private: std::string name; public: bird(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~bird() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, bird& ip_bird) { out_stream << ip_bird.name; return out_stream; } }; class fish { private: std::string name; public: fish(std::string ip_name) { name = ip_name; std::cout << "Born" << std::endl; } ~fish() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, fish& ip_fish) { out_stream << ip_fish.name; return out_stream; } }; template <class T> class simple_integer_random_generator { private: unsigned int range_from; unsigned int range_to; T* p_generator; std::uniform_int_distribution<int>* p_distribution; public: simple_integer_random_generator(unsigned int ip_range_from, unsigned int ip_range_to) : range_from(ip_range_from), range_to(ip_range_to) { /* no checks for range_from and range_to */ std::random_device rd; //Seed p_generator = new T(rd()); p_distribution = new std::uniform_int_distribution<int>(range_from, range_to); } ~simple_integer_random_generator() { delete p_distribution; delete p_generator; } unsigned int get_rand(void) { return (*p_distribution)(*p_generator); } }; std::any nature_factory(void) { static simple_integer_random_generator<std::minstd_rand> random_engine(1,3); switch(random_engine.get_rand()) { case 1: return new human("Human");break; case 2: return new bird("Bird");break; case 3: default: break; } return new fish("Fish"); } int main() { auto p_being = nature_factory(); std::regex human_pattern {"human"}; std::regex bird_pattern {"bird"}; std::regex fish_pattern {"fish"}; if(std::regex_search(p_being.type().name(), human_pattern)) { std::cout << "It is a " << *(std::any_cast<human*>(p_being)) << std::endl; } else if(std::regex_search(p_being.type().name(), bird_pattern)) { std::cout << "It is a " << *(std::any_cast<bird*>(p_being)) << std::endl; } else if(std::regex_search(p_being.type().name(), fish_pattern)) { std::cout << "It is a " << *(std::any_cast<fish*>(p_being)) << std::endl; } // ensure to delete if(std::regex_search(p_being.type().name(), human_pattern)) { delete (std::any_cast<human*>(p_being)); } else if(std::regex_search(p_being.type().name(), bird_pattern)) { delete (std::any_cast<bird*>(p_being)); } else if(std::regex_search(p_being.type().name(), fish_pattern)) { delete (std::any_cast<fish*>(p_being)); } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
64) Using polymorphic_allocator (C++17) |
new uses heap memory of object creation.
C++17 introduced a way to use the concept of pool memory for dynamic object creation.
Below example uses vector and list creation from a pool. There are additional traces to ensure that there is no memory leak. #include <iostream> #include <string> #include <vector> #include <list> #include <memory_resource> class human { protected: std::string name; public: human() { std::cout << "Born" << std::endl; } ~human() { std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man() {} man(std::string ip_name) { name = ip_name; } }; class boy : public man { public: boy(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman() {} woman(std::string ip_name) { name = ip_name; } }; class girl : public woman { public: girl(std::string ip_name) { name = ip_name; } }; //Policy#1 - Store in vector with pool template<typename IT, typename AT> struct vector_store_with_pool { using item_type = IT; using allocator_type = AT; std::vector<item_type*, allocator_type>* p_storage = nullptr; void create(allocator_type& allocator) { p_storage = new std::vector<item_type*, allocator_type>(allocator); std::cout << "create" << std::endl; } void destroy(void) { delete p_storage; std::cout << "destroy" << std::endl; } void add_item(item_type* item) { p_storage->push_back(item); } void print_items(void) { for(auto* item:*p_storage) std::cout << *item << std::endl; } }; //Policy#2 - Store in list with pool template<typename IT, typename AT> struct list_store_with_pool { using item_type = IT; using allocator_type = AT; std::list<item_type*, allocator_type>* p_storage = nullptr; void create(allocator_type& allocator) { p_storage = new std::list<item_type*, allocator_type>(allocator); std::cout << "create" << std::endl; } void destroy(void) { delete p_storage; std::cout << "destroy" << std::endl; } void add_item(item_type* item) { p_storage->push_back(item); } void print_items(void) { for(auto* item:*p_storage) std::cout << *item << std::endl; } }; template<typename store_policy> class group_with_pool { private: store_policy store; public: group_with_pool(store_policy::allocator_type& allocator) { store.create(allocator); } ~group_with_pool() { store.destroy(); } void add_member(store_policy::item_type& item) { store.add_item(&item); } void print_group(void) { store.print_items(); } }; int main() { man Father("Father"); woman Mother("Mother"); boy Son("Son"); girl Daughter("Daughter"); std::pmr::synchronized_pool_resource common_pool; std::pmr::polymorphic_allocator<human*> allocator(&common_pool); group_with_pool<vector_store_with_pool<human, std::pmr::polymorphic_allocator<human*>>> poolVectorFamily(allocator); poolVectorFamily.add_member(Father); poolVectorFamily.add_member(Mother); poolVectorFamily.add_member(Son); poolVectorFamily.add_member(Daughter); std::cout << "poolVectorFamily includes .... " << std::endl; poolVectorFamily.print_group(); group_with_pool<list_store_with_pool<human, std::pmr::polymorphic_allocator<human*>>> poolListFamily(allocator); poolListFamily.add_member(Father); poolListFamily.add_member(Mother); poolListFamily.add_member(Son); poolListFamily.add_member(Daughter); std::cout << "poolListFamily includes .... " << std::endl; poolListFamily.print_group(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
65) Using for_each |
Example of map iterator rewritten with for_each.
#include <iostream> #include <string> #include <map> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } void operator=(human& cp_human) = delete; }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; int main() { man Customer1("Customer1"), Customer2("Customer2"), Customer3("Customer3"); std::map<std::string, unsigned int> restaurant_orders; unsigned int order_ctr = 0; restaurant_orders[Customer2.get_name()] = ++order_ctr; //1 restaurant_orders[Customer1.get_name()] = ++order_ctr; //2 restaurant_orders[Customer3.get_name()] = ++order_ctr; //3 std::map<std::string, unsigned int>::iterator index_to_orders; std::cout << "Iterating customer and their orders (using for_each and lamda expression) ..." << std::endl; for_each(restaurant_orders.begin(), restaurant_orders.end(), [](const std::pair<std::string, unsigned int>& first_second) { std::cout << "\t" << first_second.first << ", order# " << first_second.second << std::endl; } ); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
66) thread and mutex |
Example of thread and mutex with a call to an object.
It is *not* necessary that print_thread_LIFO will be called always before print_thread_FIFO. #include <iostream> #include <string> #include <list> #include <thread> #include <mutex> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group_LIFO(std::mutex* p_print_lock) { if(members == nullptr) return; p_print_lock->lock(); std::cout << "Soccer team LIFO:" << std::endl; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } p_print_lock->unlock(); } void print_group_FIFO(std::mutex* p_print_lock) { if(members == nullptr) return; p_print_lock->lock(); std::cout << "Soccer team FIFO:" << std::endl; for(auto reverse_member_iterator = members->rbegin(); reverse_member_iterator != members->rend(); ++reverse_member_iterator) { T* member = *reverse_member_iterator; std::cout << *member << std::endl; } p_print_lock->unlock(); } void remove_member(T& person) { members->remove(&person); } }; using mens_group = group<man>; int main() { mens_group mens_soccer_team; man Man1("Man1"); man Man2("Man2"); man Man3("Man3"); man Man4("Man4"); mens_soccer_team.add_member(Man1); mens_soccer_team.add_member(Man2); mens_soccer_team.add_member(Man3); mens_soccer_team.add_member(Man4); std::mutex print_lock; std::thread print_thread_LIFO ( &mens_group::print_group_LIFO, &mens_soccer_team, &print_lock ); std::thread print_thread_FIFO ( &mens_group::print_group_FIFO, &mens_soccer_team, &print_lock ); print_thread_LIFO.join(); print_thread_FIFO.join(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
67) Another example of thread and mutex |
This is one possible way to implement communication between two threads.
We have also used chrono from standard library. #include <iostream> #include <string> #include <any> #include <random> #include <regex> #include <thread> #include <chrono> class human { private: std::string name; static unsigned int population; public: human(std::string ip_name) { name = ip_name; ++population; //std::cout << "Born" << std::endl; } ~human() { //std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } static unsigned int get_population(void) { return population; } }; unsigned int human::population = 0; class bird { private: std::string name; static unsigned int population; public: bird(std::string ip_name) { name = ip_name; ++population; //std::cout << "Born" << std::endl; } ~bird() { //std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, bird& ip_bird) { out_stream << ip_bird.name; return out_stream; } static unsigned int get_population(void) { return population; } }; unsigned int bird::population = 0; class fish { private: std::string name; static unsigned int population; public: fish(std::string ip_name) { name = ip_name; ++population; //std::cout << "Born" << std::endl; } ~fish() { //std::cout << "Died" << std::endl; } friend std::ostream& operator<<(std::ostream& out_stream, fish& ip_fish) { out_stream << ip_fish.name; return out_stream; } static unsigned int get_population(void) { return population; } }; unsigned int fish::population = 0; template <class T> class simple_integer_random_generator { private: unsigned int range_from; unsigned int range_to; T* p_generator; std::uniform_int_distribution<int>* p_distribution; public: simple_integer_random_generator(unsigned int ip_range_from, unsigned int ip_range_to) : range_from(ip_range_from), range_to(ip_range_to) { /* no checks for range_from and range_to */ std::random_device rd; //Seed p_generator = new T(rd()); p_distribution = new std::uniform_int_distribution<int>(range_from, range_to); } ~simple_integer_random_generator() { delete p_distribution; delete p_generator; } unsigned int get_rand(void) { return (*p_distribution)(*p_generator); } }; std::any nature_factory(void) { static simple_integer_random_generator<std::minstd_rand> random_engine(1,3); switch(random_engine.get_rand()) { case 1: return new human("Human");break; case 2: return new bird("Bird");break; case 3: default: break; } return new fish("Fish"); } enum class flag_type { free, nature_in_progress, population_consensus_in_progress }; struct sync_lock_type { std::mutex mutex_lock; flag_type flag ; }; void run_nature_factory(unsigned int time_microseconds, sync_lock_type* p_sync_lock) { auto time_start = std::chrono::high_resolution_clock::now(); auto time_check = std::chrono::high_resolution_clock::now(); static std::vector<human*> human_world; static std::vector<bird*> bird_world; static std::vector<fish*> fish_world; const static std::regex human_pattern {"human"}; const static std::regex bird_pattern {"bird"}; const static std::regex fish_pattern {"fish"}; bool go_ahead = false; do { go_ahead = false; p_sync_lock->mutex_lock.lock(); if(p_sync_lock->flag != flag_type::population_consensus_in_progress) { p_sync_lock->flag = flag_type::nature_in_progress; go_ahead = true; } p_sync_lock->mutex_lock.unlock(); if(go_ahead) { auto p_being = nature_factory(); if(std::regex_search(p_being.type().name(), human_pattern)) { std::cout << "It is a " << *(std::any_cast<human*>(p_being)) << std::endl; human_world.push_back(std::any_cast<human*>(p_being)); } else if(std::regex_search(p_being.type().name(), bird_pattern)) { std::cout << "It is a " << *(std::any_cast<bird*>(p_being)) << std::endl; bird_world.push_back(std::any_cast<bird*>(p_being)); } else if(std::regex_search(p_being.type().name(), fish_pattern)) { std::cout << "It is a " << *(std::any_cast<fish*>(p_being)) << std::endl; fish_world.push_back(std::any_cast<fish*>(p_being)); } p_sync_lock->mutex_lock.lock(); p_sync_lock->flag = flag_type::free; p_sync_lock->mutex_lock.unlock(); } time_check = std::chrono::high_resolution_clock::now(); } while( std::chrono::duration_cast<std::chrono::microseconds>(time_check - time_start).count() < time_microseconds ); go_ahead = false; do { p_sync_lock->mutex_lock.lock(); if(p_sync_lock->flag != flag_type::population_consensus_in_progress) { p_sync_lock->flag = flag_type::nature_in_progress; go_ahead = true; } p_sync_lock->mutex_lock.unlock(); } while(go_ahead == false); for(auto p_human:human_world) { delete p_human; } for(auto p_bird:bird_world) { delete p_bird; } for(auto p_fish:fish_world) { delete p_fish; } p_sync_lock->mutex_lock.lock(); p_sync_lock->flag = flag_type::free; p_sync_lock->mutex_lock.unlock(); } void population_consensus(unsigned int time_microseconds, sync_lock_type* p_sync_lock, unsigned int break_period) { auto time_start = std::chrono::high_resolution_clock::now(); auto time_check = std::chrono::high_resolution_clock::now(); unsigned int human_population; unsigned int bird_population; unsigned int fish_population; bool go_ahead = false; do { go_ahead = false; p_sync_lock->mutex_lock.lock(); if(p_sync_lock->flag != flag_type::nature_in_progress) { p_sync_lock->flag = flag_type::population_consensus_in_progress; go_ahead = true; } p_sync_lock->mutex_lock.unlock(); if(go_ahead) { human_population = human::get_population(); bird_population = bird::get_population(); fish_population = fish::get_population(); std::cout << "Human(" << human_population << "), Bird(" << bird_population << "), Fish(" << fish_population << ")" << std::endl; p_sync_lock->mutex_lock.lock(); p_sync_lock->flag = flag_type::free; p_sync_lock->mutex_lock.unlock(); std::this_thread::sleep_for(std::chrono::microseconds{break_period}); } time_check = std::chrono::high_resolution_clock::now(); } while( std::chrono::duration_cast<std::chrono::microseconds>(time_check - time_start).count() < time_microseconds ); } int main() { const unsigned int one_life = 1000000; const unsigned int consensus_break_period = 1000; sync_lock_type sync_lock; std::thread life_cycle(run_nature_factory, one_life, &sync_lock); std::thread population_check(population_consensus, one_life, &sync_lock, consensus_break_period); life_cycle.join(); population_check.join(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
68) thread_local |
Bjarne - "A thread_local resembles a local variable, but a local variable has its lifetime
and access limited by its scope within a function,
whereas a thread_local is shared among all functions of a thread and ‘‘lives’’ for as long as the thread." It makes sense that thread_local is usually applied to static variables. However, we need to be sure of is this what we want. Without thread_local #include <iostream> #include <thread> #include <mutex> class human { private: static int population_ctr; public: human() { ++population_ctr; } ~human() { --population_ctr; } static int population(void) { return population_ctr; } }; int human::population_ctr = 0; class man : public human { public: man() {} ~man() {} }; void generate_men(unsigned short how_many, std::mutex* p_print_lock) { while(how_many--) { man temp_man; } p_print_lock->lock(); std::cout << "Population is " << human::population() << std::endl; p_print_lock->unlock(); }; int main() { std::mutex print_lock; std::thread thread1(generate_men, 100000, &print_lock); std::thread thread2(generate_men, 100000, &print_lock); thread1.join(); thread2.join(); std::cout << "----------" << std::endl; return 0; }
With thread_local #include <iostream> #include <thread> #include <mutex> class human { private: thread_local static int population_ctr; public: human() { ++population_ctr; } ~human() { --population_ctr; } static int population(void) { return population_ctr; } }; thread_local int human::population_ctr = 0; class man : public human { public: man() {} ~man() {} }; void generate_men(unsigned short how_many, std::mutex* p_print_lock) { while(how_many--) { man temp_man; } p_print_lock->lock(); std::cout << "Population is " << human::population() << std::endl; p_print_lock->unlock(); }; int main() { std::mutex print_lock; std::thread thread1(generate_men, 100000, &print_lock); std::thread thread2(generate_men, 100000, &print_lock); thread1.join(); thread2.join(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
69) Automatic template argument deduction (C++17) |
Sometimes compiler can deduce template argument from the constructor arguments as shown in below example.
Example also shows usage of initializer_list. #include <iostream> #include <string> #include <list> #include <vector> class human { protected: std::string name; public: virtual std::string get_name(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } std::string get_name(void) { return name; } }; template<typename T> class group { private: std::list<T*> *members; public: group() { members = new std::list<T*>; } group(std::initializer_list<T*> ip_list) { members = new std::list<T*>; for(auto ip_member:ip_list) { add_member(*ip_member); } } ~group() { delete members; } void add_member(T& person) { auto member_iterator = members->begin(); members->insert(member_iterator, &person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { T* member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(T& person) { members->remove(&person); } }; int main() { man* Man1 = new man("Man1"); man* Man2 = new man("Man2"); group<man> mens_soccer_team{Man1, Man2}; //template argument explicitly mentioned std::cout << "Soccer team:" << std::endl; mens_soccer_team.print_group(); woman* Woman1 = new woman("Woman1"); woman* Woman2 = new woman("Woman2"); group womens_kitty_group{Woman1, Woman2}; //template argument deduction std::cout << "Kitty group:" << std::endl; womens_kitty_group.print_group(); //clean-up delete Man1; delete Man2; delete Woman1; delete Woman2; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
70) Template argument deduction guide (C++17) |
If the constructor arguments are *not* sufficient to deduce the template argument (type),
a guide can be provided.
"if you come across group of (iter,iter), please map it to group of iter's value type" #include <iostream> #include <string> #include <list> #include <vector> class human { protected: std::string name; public: friend std::ostream& operator<<(std::ostream& out_stream, human& ip_human) { out_stream << ip_human.name; return out_stream; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; class woman : public human { public: woman(std::string ip_name) { name = ip_name; } }; template<typename TP> class group { private: std::list<TP> *members; public: group() { members = new std::list<TP>; } group(std::initializer_list<TP> ip_list) { members = new std::list<TP>; for(auto ip_member:ip_list) { add_member(ip_member); } } template<typename iter> group(iter iterator_begin, iter iterator_end) { members = new std::list<TP>; for(iter local_iterator = iterator_begin; local_iterator != iterator_end; ++local_iterator) { add_member(*local_iterator); } } ~group() { delete members; } void add_member(TP person) { auto member_iterator = members->begin(); members->insert(member_iterator, person); } void print_group(void) { if(members == nullptr) return; for(auto member_iterator = members->begin(); member_iterator != members->end(); ++member_iterator) { TP member = *member_iterator; std::cout << *member << std::endl; } } void remove_member(TP person) { members->remove(person); } }; // template argument deduction guide // if you come across group of (iter,iter), please map it to group of iter's value type template<typename iter> group(iter iterator_begin, iter iterator_end) -> group<typename iter::value_type>; int main() { man* Man1 = new man("Man1"); man* Man2 = new man("Man2"); group<man*> mens_soccer_team{Man1, Man2}; //template argument explicitly mentioned std::cout << "Soccer team:" << std::endl; mens_soccer_team.print_group(); woman* Woman1 = new woman("Woman1"); woman* Woman2 = new woman("Woman2"); group womens_kitty_group{Woman1, Woman2}; //template argument deduction std::cout << "Kitty group:" << std::endl; womens_kitty_group.print_group(); woman* Woman3 = new woman("Woman3"); woman* Woman4 = new woman("Woman4"); std::vector<woman*> new_residents{Woman3, Woman4}; // argument deduction using user provided guide group new_womens_kitty_group(new_residents.begin(), new_residents.end()); std::cout << "New Kitty group:" << std::endl; new_womens_kitty_group.print_group(); //clean-up delete Man1; delete Man2; delete Woman1; delete Woman2; delete Woman3; delete Woman4; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
71) decltype, typeid |
Below is simple illustration of decltype,
typeid, and operator+ overloading.
#include <iostream> class man { public: man() {} }; class spider { public: spider() {} }; class bat_costume { public: bat_costume() {} }; class superhero { public: superhero() {} }; template<typename T, typename U> auto operator+(const T& a, const U& b) -> decltype(superhero{}); // template argument deduction guide in declaration template<typename T, typename U> auto operator+(const T& a, const U& b) -> decltype(superhero{}) // must specify it again in definition { superhero superhero_born; std::cout << "Superhero born from '" << typeid(a).name() << "' and '" << typeid(b).name() << "'" << std::endl; return superhero_born; } int main() { man A_normal_guy; spider A_mystry_spider; decltype(A_normal_guy + A_mystry_spider) A_Spiderman = A_normal_guy + A_mystry_spider; man A_rich_guy; bat_costume A_bat_costume; auto A_Batman = A_rich_guy + A_bat_costume; // we could use "decltype(man{} + bat_costume{})" or "decltype(A_rich_guy + A_bat_costume)" instead of "auto" std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
72) decltype(auto) (C++14) |
decltype example can be rewritten with decltype(auto) or simply with auto.
With decltype(auto) #include <iostream> class man { public: man() {} }; class spider { public: spider() {} }; class bat_costume { public: bat_costume() {} }; class superhero { public: superhero() {} }; template<typename T, typename U> decltype(auto) operator+(const T& a, const U& b); template<typename T, typename U> decltype(auto) operator+(const T& a, const U& b) { superhero superhero_born; std::cout << "Superhero born from '" << typeid(a).name() << "' and '" << typeid(b).name() << "'" << std::endl; return superhero_born; } int main() { man A_normal_guy; spider A_mystry_spider; decltype(auto) A_Spiderman = A_normal_guy + A_mystry_spider; man A_rich_guy; bat_costume A_bat_costume; auto A_Batman = A_rich_guy + A_bat_costume; std::cout << "----------" << std::endl; return 0; }
With auto #include <iostream> class man { public: man() {} }; class spider { public: spider() {} }; class bat_costume { public: bat_costume() {} }; class superhero { public: superhero() {} }; template<typename T, typename U> auto operator+(const T& a, const U& b); template<typename T, typename U> auto operator+(const T& a, const U& b) { superhero superhero_born; std::cout << "Superhero born from '" << typeid(a).name() << "' and '" << typeid(b).name() << "'" << std::endl; return superhero_born; } int main() { man A_normal_guy; spider A_mystry_spider; auto A_Spiderman = A_normal_guy + A_mystry_spider; man A_rich_guy; bat_costume A_bat_costume; auto A_Batman = A_rich_guy + A_bat_costume; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
73) auto versus decltype(auto) |
Below example illustrate difference of behaviour between auto versus decltype(auto).
#include <iostream> #include <string> enum class emotion_type { HAPPY, OVERJOYED }; std::ostream& operator<<(std::ostream& out_stream, emotion_type emotion) { switch(emotion) { case emotion_type::HAPPY: out_stream << "Happy"; break; case emotion_type::OVERJOYED: out_stream << "Overjoyed"; break; default: out_stream << "<unknown>"; break; } return out_stream; } enum class aptitude_type { DREAMER, ACHIEVER }; std::ostream& operator<<(std::ostream& out_stream, aptitude_type aptitude) { switch(aptitude) { case aptitude_type::DREAMER: out_stream << "Dreamer"; break; case aptitude_type::ACHIEVER: out_stream << "Achiever"; break; default: out_stream << "<unknown>"; break; } return out_stream; } class human { private: emotion_type my_emotion = emotion_type::HAPPY; aptitude_type my_aptitude = aptitude_type::DREAMER; public: emotion_type& emotion(void) { return my_emotion; } const aptitude_type& aptitude(void) { return my_aptitude; } decltype(auto) get_emotion(void) { return my_emotion; } decltype(auto) get_aptitude(void) { return (my_aptitude); // brackets around return (variable) name changes type } }; int main() { human Human; auto emotion = Human.emotion(); // emotion will be of type "emotion_type" std::cout << Human.emotion() << std::endl; emotion = emotion_type::OVERJOYED; std::cout << Human.emotion() << std::endl; std::cout << "----------" << std::endl; decltype(auto) emotion2 = Human.emotion(); // emotion2 will be of type "emotion_type&" std::cout << Human.emotion() << std::endl; emotion2 = emotion_type::OVERJOYED; std::cout << Human.emotion() << std::endl; std::cout << "----------" << std::endl; auto aptitude = Human.aptitude(); // aptitude will be of type "aptitude_type" std::cout << Human.aptitude() << std::endl; aptitude = aptitude_type::ACHIEVER; std::cout << Human.aptitude() << std::endl; std::cout << "----------" << std::endl; decltype(auto) aptitude2 = Human.aptitude(); // aptitude2 will be of type "const aptitude_type&" /* below statement will raise compiler error "error: assignment of read-only reference ‘aptitude2’" aptitude2 = aptitude_type::ACHIEVER; */ std::cout << "----------" << std::endl; decltype(auto) emotion3 = Human.get_emotion(); // emotion will be of type "emotion_type" std::cout << Human.emotion() << std::endl; emotion3 = emotion_type::OVERJOYED; std::cout << Human.emotion() << std::endl; std::cout << "----------" << std::endl; decltype(auto) aptitude3 = Human.get_aptitude(); // aptitude will be of type "aptitude_type&" std::cout << Human.aptitude() << std::endl; aptitude3 = aptitude_type::ACHIEVER; std::cout << Human.aptitude() << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
74) Attribute [[noreturn]] |
[[ ]] is called an attribute.
Below is an example of [[noreturn]]. Another attribute is [[carries_dependency]] - relevant to memory model. #include <iostream> #include <string> class human { protected: std::string name; public: ~human() { std::cout << "Died" << std::endl; } }; class man : public human { public: man(std::string ip_name) { name = ip_name; } }; [[noreturn]] void black_hole(human* p_human) { delete p_human; std::exit(1); } int main() { man* Man1 = new man("Man1"); black_hole(Man1); delete(Man1); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
75) Attribute [[deprecated]] (C++14) |
As the word suggests, idea is to warn the users that the function is obsolete and
it is to be avoided.
Compiler will print a warning if such function is called. #include <iostream> class human { public: [[deprecated]] void work_70_hours_per_week(void) {} }; int main() { human Worker; Worker.work_70_hours_per_week(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
76) Structured binding (C++17) |
Bjarne = "The mechanism for giving local names to members of a class object is called structured binding".
#include <iostream> #include <string> #include <map> struct record_type; class human { public: enum class profession_type { person, student, teacher, engineer, businessman }; virtual record_type get_record(void) = 0; friend std::ostream& operator<<(std::ostream& out_stream, profession_type profession) { std::string profession_str; switch(profession) { default: case profession_type::person: profession_str = "Person"; break; case profession_type::student: profession_str = "Student"; break; case profession_type::teacher: profession_str = "Teacher"; break; case profession_type::engineer: profession_str = "Engineer"; break; case profession_type::businessman: profession_str = "Businessman"; break; } out_stream << profession_str; return out_stream; } protected: std::string name; profession_type profession; }; struct record_type { std::string name; human::profession_type profession; }; class man : public human { public: man(std::string ip_name, human::profession_type ip_profession) { name = ip_name; profession = ip_profession; } record_type get_record(void) { return {name, profession}; } }; int main() { man Man1("Man1", human::profession_type::engineer); auto record = Man1.get_record(); std::cout << record.name << " is " << record.profession << "." << std::endl; man Man2("Man2", human::profession_type::student); auto [name2, profession2] = Man2.get_record(); // structured binding std::cout << name2 << " is " << profession2 << "." << std::endl; auto [name1, profession1 ] = record; // structured binding std::map<std::string, human::profession_type> record_map { { name1, profession1 }, { name2, profession2 } }; for( auto [key, value] : record_map ) // structured binding { std::cout << key << " is " << value << "." << std::endl; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
77) Attribute [[fallthrough]] (C++17) |
As the name suggests, [[fallthrough]] is useful in switch case statement.
#include <iostream> #include <string> #include <vector> class human { public: enum class profession_type { person, student, teacher, engineer, businessman }; protected: unsigned short age; std::string name; profession_type profession; }; class man : public human { public: man(std::string ip_name, human::profession_type ip_profession, unsigned short ip_age) { name = ip_name; profession = ip_profession; age = ip_age; } std::string get_name() { return name; } profession_type get_profession() { return profession; } unsigned short get_age() { return age; } }; int main() { man Man1("Jack", human::profession_type::engineer, 32); man Man2("John", human::profession_type::student, 18); man Man3("Doe", human::profession_type::student, 18); man Man4("Chris", human::profession_type::teacher, 40); std::vector<man*> candidates; candidates.push_back(&Man1); candidates.push_back(&Man2); candidates.push_back(&Man3); candidates.push_back(&Man4); // looking for student of age 18, but not named 'Doe' for(auto candidate:candidates) { switch(candidate->get_profession()) { case human::profession_type::student: { if(candidate->get_name() == "Doe") { [[fallthrough]]; } else if(candidate->get_age() == 18) { std::cout << "Got it, his name is " << candidate->get_name() << std::endl; break; } } default: { std::cout << candidate->get_name() << " - no match " << std::endl; } } } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
78) Generalised or Nontrivial union |
union is said to be nontrivial when one it its member is a class with constructors/destructor,
like in the example below.
C++ has set some conditions on such union (due to its nature). One notable condition is union (or class containing it) must explicitly define constructor and destructor. Please refer "The C++ Programming Language (4th edition) by Bjarne Stroustrup", chapter 8.3 for more details. #include <iostream> #include <vector> class man { public: std::string name; }; class woman { public: std::string name; }; class employee { public: enum class gender_t { male, female }; // nontrivial union union { man Man; woman Woman; }; gender_t gender; employee() // mandatory constructor { std::cout << "employee()" << std::endl; } ~employee() // mandatory destructor { std::cout << "~employee()" << std::endl; } void operator=(const man& ip_man) { Man = ip_man; gender = gender_t::male; } void operator=(const woman& ip_woman) { Woman = ip_woman; gender = gender_t::female; } friend std::ostream& operator<<(std::ostream& out_stream, employee& ip_employee) { switch(ip_employee.gender) { case gender_t::male: out_stream << ip_employee.Man.name; break; case gender_t::female: out_stream << ip_employee.Woman.name; break; default: break; } return out_stream; } }; int main() { man Man1; woman Woman1; Man1.name = "Man1"; Woman1.name = "Woman1"; std::vector<employee*> Employees; employee Employee1; employee Employee2; Employee1 = Man1; Employee2 = Woman1; Employees.push_back(&Employee1); Employees.push_back(&Employee2); std::cout << "Employees are ... " << std::endl; for(auto employee_iterator:Employees) std::cout << *employee_iterator << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
79) Unicode string literal |
In C++17 (should work in previous versions as well)
#include <iostream> int main() { // Unicode literal in UTF-8 const char* Devanagiri_script_characters = u8"\u0905, \u0906, \u0907, and \u0908."; std::cout << "First 4 letters of Devanagiri script are: " << std::endl; std::cout << Devanagiri_script_characters << std::endl; std::cout << "----------" << std::endl; return 0; }
In C++20 #include <iostream> int main() { // Unicode literal in UTF-8 const char8_t* Devanagiri_script_characters = u8"\u0905, \u0906, \u0907, and \u0908."; std::cout << "First 4 letters of Devanagiri script are: " << std::endl; std::cout << reinterpret_cast<const char*>(Devanagiri_script_characters) << std::endl; std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
80) User defined literal |
With operator"", we can define our own literals.
#include <iostream> enum class feeling_type { content, bored, happy, sad }; feeling_type operator ""_feeling(const char* f, size_t n) { std::string feeling = std::string{f,n}; if(feeling == "bored") return feeling_type::bored; if(feeling == "happy") return feeling_type::happy; if(feeling == "sad") return feeling_type::sad; return feeling_type::content; } int main() { // user defined literal feeling_type feeling = "content"_feeling; switch(feeling) { case feeling_type::content: std::cout << "Finally, a 'content' feeling." << std::endl; break; case feeling_type::happy: std::cout << "Best feeling of being 'happy'." << std::endl; break; case feeling_type::sad: std::cout << "Don't be 'sad'." << std::endl; break; case feeling_type::bored: default: std::cout << "Why so 'bored' ?" << std::endl; break; } std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
81) Concept of "value" |
| ||||||||||||||||||||||||
82) Member function's reference qualifier |
With reference qualifier, member function call can be controlled to be on lvalue reference or rvalue reference.
#include <iostream> #include <string> class worker { private: std::string name; public: worker(std::string ip_name) : name(ip_name) { std::cout << "worker(" << name << ")" << std::endl; } void do_the_job(void) & // lvalue reference { std::cout << "do_the_job() lvalue reference" << std::endl; } void do_the_job(void) && // rvalue reference { std::cout << "do_the_job() rvalue reference" << std::endl; } ~worker() { std::cout << "~worker(" << name << ")" << std::endl; } }; worker temporary_worker_factory(std::string ip_name) { return *(new worker(ip_name)); } int main() { worker Worker("Worker"); Worker.do_the_job(); temporary_worker_factory("Temp").do_the_job(); std::cout << "----------" << std::endl; return 0; }
| ||||||||||||||||||||||||
83) Glossary |
|
© Copyright Samir Amberkar 2023-24