More general than special
Inheritance constructor
Subclasses automatically get the member variables and interfaces of the parent class
Interfaces refer to virtual functions and pure virtual functions
The implication is that a non virtual function of a parent class cannot be inherited by a child class
Constructors also follow this rule. If the constructor of the parent class is not a virtual function, it will not be inherited by the child class
During the initialization of subclasses, the parent class must be initialized first,
If the constructor of the parent class is not a virtual function,
Then the subclass cannot inherit the constructor of the parent class,
Therefore, it cannot be initialized for the parent class,
Generally, the declaration displayed by the subclass in its constructor uses the constructor of the parent class, which solves the problem that the constructor of the parent class can initialize the parent class even if it is not a virtual function
struct A { A(int i){} //Constructors without virtual are not virtual functions and cannot be inherited by subclasses }; struct B : public A { B(int i) : A(i) {} //The declaration shown uses the constructor of the parent class to resolve the constructor that cannot inherit the parent class };
Problem: if the base class has many construction methods and these methods are not virtual functions, in order to use the construction methods of the parent class, the subclass must write a constructor corresponding to the subclass for the construction of each parent class, so the programmer is tired
struct A{ A(int i){} A(double d, int i){} A(float f, int i, const char * c) {} //... Then there are 100000 }; struct B : public A { B(int i):A(i){} B(double d, int i):A(d, i){} B(float f, int i, const char * c) : A(f, i, c) {} //... Later, suppose 100000 programmers who write class B are tired to death virtual void ExtraInterface{} };
The purpose of B is to write an extended interface or function ExtraInterface() {} but to initialize the parent class, B must display the version corresponding to the constructor of more than 100000 parent classes. At this time, the programmer who wrote class B immediately gave up c++
To solve this problem? c + + uses using to declare constructors that inherit from base classes
struct A{ A(int i){} A(double d, int i){} A(float f, int i, const char * c) {} //... Then there are 100000 }; struct B : public A { using A::A; //Inheritance constructor //... It's easy to assume that there are 100000 programmers who write class B virtual void ExtraInterface{} };
Question: the inheritance constructor will only initialize the members of the parent class. The members of the child class cannot be initialized. What should I do?
Then, in combination with in place initialization, a default value is given to subclass members
struct A{ A(int i){} A(double d, int i){} A(float f, int i, const char * c) {} }; struct B : public A { using A::A; //Inheritance constructor virtual void ExtraInterface{} int b{10}; //Initialize a default value in place. In this way, class members can also be initialized };
Problem: if the subclass needs external parameters to initialize the subclass member variables, the programmer can only implement the construction method by himself. Of course, local initialization can also exist, but the priority of list initialization is high, so the parameters of initialization list are taken
struct A{ A(int i){} A(double d, int i){} A(float f, int i, const char * c) {} }; struct B : public A { B(int i, int j)):A(i),b(j){} B(double b, inti, int j):A(d, i),b(j){} B(float f, int i, const char * c, int j):A(f,i,c),b(j){} virtual void ExtraInterface{} int b{10}; };
Problem: sometimes the parameters of the parent constructor have default values. For inherited constructors, the default values of parameters are not inherited. The default value will cause the parent class to produce multiple constructor versions, which will be inherited by the derived class
struct A { A (int a = 3, double = 2.4){} }; struct B : public A{ using A:A; }; //Multiple constructors are generated for A struct A { A(); //My case without parameters A(int a, double = 2.4); //Use of one parameter A(int a, double b); //Use of two parameters A(const A &); //Default Copy Constructor }; //If the corresponding B inherits A, there will be multiple inherited constructors struct B : public A{ B():A(); //My case without parameters B(int a, double = 2.4):B(a, 2.4); //My case without parameters B(int a, double b):A(a,b); //Use of two parameters B(const B &rhs):A(rhs); //Default Copy Constructor };
The default value of the parameter will result in multiple constructor versions, so programmers should be very careful
Problem: in case of multiple inheritance, the subclass has multiple superclasses, so the constructors in multiple superclasses will cause the function names and parameters to be the same, resulting in conflicting ambiguities
struct A { A(int a){} }; struct B { B(int b){} }; struct C:A,B { using A:A; using B:B; }; //The solution is to declare the constructor of C struct C:A,B { using A:A; using B:B; C(int){} };
Question:
If the constructor of the parent class is declared as a private member function or the subclass is virtually inherited from the parent class, the inherited constructor cannot be declared in the subclass
If the subclass uses an inheritance constructor, the compiler will no longer generate a default constructor for the subclass, so the programmer needs to manually write a parameterless constructor
#include <iostream> struct A { A(int){} }; struct B:public A{ using A::A; }; int main(int argc, char* argv[], char **env) { B b; //Call the implicitly deleted "B" default constructor return EXIT_SUCCESS; }
Delegating constructors
The purpose is to reduce the time for programmers to write constructors
By delegating other constructors, classes with multiple constructors can reduce a lot of code
class Info{ public: Info():type(1),name('a') {InitRest();} Info(int i):type(i),name('a') {InitRest();} Info(char e):type(1),name(e) {InitRest();} private: void InitRest(){} int type; char name; }; //It is found that each constructor uses the initialization list to initialize type and name, and calls the same function InitRest() //Except for the differences in the initialization list, the other parts are the same. The three constructors are basically similar, and there are duplicates in the code //Transformation 1 is indeed a lot easier to use local initialization, but each still calls InitRest() class Info{ public: Info() {InitRest();} Info(int i):type(i) {InitRest();} Info(char e):name(e) {InitRest();} private: void InitRest(){} int type{1}; char name{'a'}; }; //Modifying the compiler again does not allow this->Info () not to call constructors in the constructor. class Info{ public: Info() {InitRest();} Info(int i) {this->Info(); type=i;} //Compiler error Info(char e){this->Info(); name = 2} //Compiler error private: void InitRest(){} int type{1}; char name{'a'}; }; //Once again, the hacker technology is transformed. Although it looks good to bypass the check of the compiler, this is to call the constructor again on an object that has already been initialized, which is not called initialization class Info{ public: Info() {InitRest();} Info(int i) {new (this) Info(); type = i;} Info(char e){new (this) Info(); name = 2;} private: void InitRest(){} int type{1}; char name{'a'}; }; //Again, use the delegate constructor //The delegate constructor can only assign initial values to members such as type and name in the function body, because the delegate constructor cannot have an initialization list (it cannot "delegate" and an initialization list at the same time) //Delegate and initialization list cannot be used at the same time (because initialization list has high priority) class Info{ public: Info() {InitRest();} //Target constructor Info(int i):Info(){type=i;} //Delegating constructors Info(char e):Info(){name = 2;} //Delegating constructors private: void InitRest(){type+=1;} int type{1}; char name{'a'}; }; //f(3) is first 4 and then assigned to 3. The target constructor executes before the delegate constructor //Again, use the delegate constructor class Info{ public: Info():Info(1,'a'){} //Delegating constructors Info(int i):Info(i,'a'){} //Delegating constructors Info(char e):Info(1,e){} //Delegating constructors private: Info(int i, char e):type(i),name(e){type+=1;} //Define a private target constructor instead of InitRest() int type; char name; }; //f(3) is 4
The chain like delegation structure should not form a delegation ring
//Normal chain like delegate structure class Info{ public: Info():Info(1){} //Delegating constructors Info(int i):Info(i,'a'){} //Delegating constructors Info(char e):Info(1,e){} //Delegating constructors private: Info(int i, char e):type(i),name(e){} //Define a private target constructor instead of InitRest() int type; char name; }; //Info() delegate Info(int) delegate Info(int,char) //Commission ring struct Rule2 { int i, c; Rule2():Rule2(2){} Rule2(int i):Rule2('c'){} Rule2(char c):Rule2(2){} }; //Rule2() delegate Rule2(int) delegate Rule2(char) delegate Rule2(int) //At this point, the compiler can't help it. It's always in this loop
The delegate constructor is applied to construct the template function to generate the target constructor
class TDConstructed{ template<class T> TDConstructed(T first, T last) : l(first, last){} //Target constructor std::list<int> l; public: TDConstructed(std::vector<short> & v):TDConstructed(v.begin(), v.end()){} //Delegating constructors TDConstructed(std::deque<int> & d):TDConstructed(d.begin(), d.end()){} //Delegating constructors }; //T will be derived as vector < short >:: iterator and deque < int >:: iterator respectively //The advantage is that it is easy to receive multiple containers and initialize them //It's more convenient than listing different types of constructors //Delegate construction makes constructor generic programming also possible
Use of delegate constructors in exceptions
#include <iostream> #include <list> class DCExcept { public: DCExcept(double d) try : DCExcept(1,d){ //The delegate constructor caught an exception and will not execute the try code std::cout << "Run the body." << std::endl; }catch (...){ std::cout << "caught exception." << std::endl; //Will execute } private: DCExcept(int i, double d){ //The target constructor threw an exception std::cout << "going to throw!" << std::endl; throw 0; } int type; double data; }; int main(int argc, char* argv[], char **env) { DCExcept a(1.2); //Since the constructor throws an exception and is not caught in the main function, call std::terminate() directly to end the program return EXIT_SUCCESS; } /** going to throw! caught exception. libc++abi: terminating with uncaught exception of type int Process finished with exit code 134 (interrupted by signal 6: SIGABRT) */
The try code of the delegate constructor satisfies that the code in the catch should not be executed
R-value reference: mobile language and perfect forwarding
Pointer member and copy constructor
If a class member has a pointer, you must be very careful to copy the constructor, otherwise memory leakage will occur accidentally
Shallow copy:
#include <iostream> class HasPtrMem{ public: HasPtrMem():d(new int(0)){} ~HasPtrMem(){delete d;} int * d; }; int main(int argc, char* argv[], char **env) { HasPtrMem a; HasPtrMem b(a); std::cout << *a.d << std::endl; //0 std::cout << *b.d << std::endl; //0 return EXIT_SUCCESS; //Normal destruct } /* primer(29288,0x1116cee00) malloc: *** error for object 0x7fdbe9405b50: pointer being freed was not allocated primer(29288,0x1116cee00) malloc: *** set a breakpoint in malloc_error_break to debug 0 0 */ //The memory on the heap will be destructed twice. The first time it is normal, the second time it is wrong
Deep copy:
#include <iostream> class HasPtrMem{ public: HasPtrMem():d(new int(0)){} HasPtrMem(const HasPtrMem &rhs):d(new int(*rhs.d)){}//The copy constructor allocates memory from the heap and initializes with * h.d ~HasPtrMem(){delete d;} int * d; }; int main(int argc, char* argv[], char **env) { HasPtrMem a; HasPtrMem b(a); std::cout << *a.d << std::endl; //0 std::cout << *b.d << std::endl; //0 return EXIT_SUCCESS; //Normal destruct }
Mobile semantics
The practice of allocating new memory for pointer members in copy constructor and then copying is almost regarded as inviolable in c + + programming
However, sometimes there is no need to copy, and copying large blocks of memory is very resource consuming and the performance is not high
#include <iostream> class HasPtrMem{ public: HasPtrMem():d(new int(0)){ std::cout << "Constructor: " << ++n_cstr << std::endl; } HasPtrMem(const HasPtrMem &rhs):d(new int(*rhs.d)){ std::cout << "copy constructor : " << ++n_cptr << std::endl; }//The copy constructor allocates memory from the heap and initializes with * h.d ~HasPtrMem(){ std::cout << "Destructor: " << ++n_dstr << std::endl; delete d; } int * d; static int n_cstr; static int n_dstr; static int n_cptr; }; int HasPtrMem::n_cstr = 0; int HasPtrMem::n_dstr = 0; int HasPtrMem::n_cptr = 0; HasPtrMem GetTemp(){ return HasPtrMem(); } int main(int argc, char* argv[], char **env) { HasPtrMem a = GetTemp(); return EXIT_SUCCESS; } /* g++ main.cpp -fno-elide-constructors Constructor: 1 Copy constructor: 1 Destructor: 1 Copy constructor: 2 Destructor: 2 Destructor: 3 */
Look at the picture
In order to get a HasPtrMem, you need to copy it twice, destruct the temporary object in the middle, and so on, so the performance is very poor
Compare copy and move models:
Add move constructor
#include <iostream> class HasPtrMem{ public: HasPtrMem():d(new int(0)){ std::cout << "Constructor: " << ++n_cstr << std::endl; } HasPtrMem(const HasPtrMem &rhs):d(new int(*rhs.d)){ std::cout << "copy constructor : " << ++n_cptr << std::endl; }//The copy constructor allocates memory from the heap and initializes with * h.d HasPtrMem(HasPtrMem && rhs):d(rhs.d){ //The move constructor first initializes the pointer of the passed object to the current object rhs.d = nullptr; //Then set the pointer of the incoming object to nullptr. Instead of copying, the pointer is directly taken in the form of assignment std::cout << "move constructor : " << ++n_mvptr << std::endl; } ~HasPtrMem(){ std::cout << "Destructor: " << ++n_dstr << std::endl; delete d; } int * d; static int n_cstr; static int n_dstr; static int n_cptr; static int n_mvptr; }; int HasPtrMem::n_cstr = 0; int HasPtrMem::n_dstr = 0; int HasPtrMem::n_cptr = 0; int HasPtrMem::n_mvptr = 0; HasPtrMem GetTemp(){ return HasPtrMem(); } int main(int argc, char* argv[], char **env) { HasPtrMem a = GetTemp(); return EXIT_SUCCESS; } /* Constructor: 1 Move constructor: 1 Destructor: 1 Move constructor: 2 Destructor: 2 Destructor: 3 */
Before and after comparison, the efficiency of copying twice and moving twice is completely different
Question: lvalue reference or pointer can achieve the same effect when the function's outgoing parameters, but why not?
There is nothing wrong with this in terms of performance, but it is poor in terms of ease of use
For example:
Caculate(GetTemp(), SomeOther(Maybe(), Useful(Values,2)));
However, using pointer and reference methods without returning values usually requires many more statements
For example:
string *a; vector b; // Declare some variables in advance
...
Useful(Values,2,a); / / the last parameter is a pointer, which is used to return the result
SomeOther(Maybe(), a, b); // The last parameter is a reference that returns the result
Caculate(GetTemp(), b);
At least you have to define a pointer and pass it to the function
However, this is not necessary for moving. Simply return the temporary quantity in the function return
In short: programmers are comfortable with using the simplest statements to complete a lot of work, and the code is also good-looking
Lvalue, rvalue and rvalue reference
Question: judge the left and right values
A classic method in c language is
In the assignment expression, what appears to the left of the equal sign is an lvalue, and what cannot be on the left is an lvalue
For example, a = b+c; A is the left-hand value, b+c = a; If the compiler reports an error, b+c is the right value
This method is sometimes easy to use and sometimes difficult to use in c + +
One method of c + + is:
Can take the address, the name is the lvalue
Those that cannot get an address and have no name are r-values
For example: a = b+c;
If the compiler does not report an error, a is an lvalue
& (b+c) the compiler reports an error, then b+c is the right value
In detail, c++11 right value consists of two concepts: Dead value and pure right value
Set default value:
Returns the return value of the function referenced by the R-value T & &
Return value of std::move
Return value of type conversion function converted to T & &
Pure right value:
The temporary variable value returned by the non referenced function is a pure right value
For example, the temporary variable generated by 1 + 3 is also a pure right value
Literal values that are not associated with the object, such as 2, 'c', true, are also pure right values
The return value of the type conversion function is also a pure right value
lambda expressions are also pure right values
Left and right values in c + + are difficult to sum up
Always remember that whether it is an lvalue reference or an lvalue reference, as long as the reference is the abbreviation of the object or the alias reference does not occupy memory, so the reference itself is not an object reference, which means that you already have a real object, I just bind to this real object, so the reference needs to be initialized at the beginning (it can also be understood that if there is no object, there is no way to bind, and there is no way to alias the object, so the reference needs to be initialized at the beginning) is nothing more than that the left value reference is bound to a named object and the right value is bound to an unnamed object (anonymous object) Also remember that once a reference is initially bound to an object, it cannot be unbound or bound to another object unless you name it another name, that is, declare a reference
How does the move constructor trigger?
How to judge the generation of temporary objects?
How do I use temporary objects for mobile constructors?
Can only temporary variables be used to move constructs?