General purpose oriented and special purpose oriented

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?

Keywords: C++

Added by MysticalCodex on Thu, 06 Jan 2022 13:54:02 +0200