C++ those things basic advanced part study notes

class size calculation

  • Empty classes have a size of 1 byte
  • In a class, the virtual function itself, member functions (including static and non-static) and static data members do not occupy the storage space of the class object.
  • For a class containing virtual functions, no matter how many virtual functions there are, there is only one virtual pointer, the size of vptr.
  • Ordinary inheritance, the derived class inherits all the functions and members of the base class, and the size should be calculated according to the byte alignment
  • Virtual function inheritance, whether it is single inheritance or multiple inheritance, inherits the vptr of the base class. (4 bytes for 32-bit operating system, 8 bytes for 64-bit operating system)!
  • Virtual inheritance, inheriting the vptr of the base class.

virtual

Pure virtual functions and abstract classes

Pure virtual functions (or abstract functions) in C++ are virtual functions that we don't implement! We just declare it! We declare a pure virtual function by assigning a value of 0 in the declaration!

// 抽象类
Class A {
    
    
public: 
    virtual void show() = 0; // 纯虚函数
    /* Other members */
}; 

Pure virtual function: a virtual function without a function body
Abstract class: a class that contains a pure virtual function

Constructors cannot be virtual and destructors can be virtual

When a base class pointer points to a derived class object and the object is deleted, we may wish to call the appropriate destructor. Only base class destructors can be called if the destructor is not virtual.

Virtual functions and runtime polymorphism

Virtual function calls depend on the type of the object pointed to or referenced, not the type of the pointer or reference itself.

Static functions cannot be declared as virtual functions, nor can they be modified by const and volatile keywords

Static member functions do not belong to any class object or class instance, so even adding virtual to this function is meaningless.
Virtual functions are handled by vptr and vtable. vptr is a pointer, created and generated in the constructor of the class, and can only be accessed with this pointer. Static member functions do not have this pointer, so vptr cannot be accessed.

Virtual functions and virtual tables

To implement virtual functions, C++ uses a special form of late binding called a virtual table. The virtual table is a lookup table for resolving function calls in a dynamic/late binding manner. Virtual tables are sometimes called by other names, such as "vtable", "virtual function table", "virtual method table", or "dispatch table".

Virtual tables are actually very simple, although a bit complicated to describe in words. First, each class that uses virtual functions (or derives from a class that uses virtual functions) has its own virtual table . The table is just a static array set by the compiler at compile time . The virtual table contains an entry for each virtual function that can be called by objects of the class . Each entry in this table is simply a function pointer to a derived function accessible by that class.
Second, the compiler also adds a hidden pointer to the base class, which we call vptr. vptr is automatically set when a class instance is created so that it points to the virtual table for that class . Unlike the this pointer, which is actually a function parameter used by the compiler to resolve self-references, vptr is a real pointer.
Therefore, it makes the allocation of each class object larger by the size of a pointer. This also means that vptr is inherited by derived classes, which is important.

The virtual table of the subclass copies the virtual table of the parent class, and the Func1 of the subclass overwrites the Func1 of the parent class. (Overwriting refers to the coverage of the virtual function in the virtual table)
Virtual function rewriting : the concept of the syntax layer, the subclass rewrites the implementation of the virtual function inherited from the parent class.
Virtual function coverage : The concept of the principle layer, the virtual table of the subclass, copy the virtual table of the parent class and modify it, and override the virtual function.
Rewriting and coverage of virtual functions, rewriting is called at the syntax level, and coverage is called at the principle level.

When the program is running, when a pointer calls a function, it will first determine the type of the object pointed to by the pointer, and then call the corresponding function according to the virtual table of the object.
insert image description here

struct

struct in C

  • In C, struct is simply used as a composite type of data, that is to say, only data members can be placed in the structure declaration, but functions cannot be placed in it.
  • C++ access modifiers such as public, protected, and private cannot be used in C structure declarations, but can be used in C++.
  • To define a structure variable in C, if the following definition is used, struct must be added. C structures cannot be inherited (there is no such concept).
  • If the name of the structure is the same as the name of the function, it can run normally and be called normally! For example: void Base() {} that does not conflict with struct Base can be defined.

struct in C++

Compare with C as follows:

  • In the C++ structure, not only data can be defined, but also functions can be defined.
  • Access modifiers can be used in C++ structures, such as: public, protected, private.
  • C++ structure can be used directly without struct.
  • C++ inheritance
  • If the name of the structure is the same as the name of the function, it can run normally and be called normally! But when defining structure variables, only those with struct can be used!

struct&class

In general, struct is more suitable to be regarded as the realization body of a data structure, and class is more suitable to be regarded as the realization body of an object.

Difference:
One of the most essential differences is the default access control
and the default inherited access rights. Struct is public and class is private.
Struct is the realization body of data structure, its default data access control is public, and class is the realization body of object, its default member variable access control is private.

union

Union (union) is a special class that saves space. A union can have multiple data members, but only one data member can have a value at any time. When a member is assigned a value, other members become undefined. Unions have the following characteristics:

  • The default access control character is public
  • Can contain constructors, destructors
  • Cannot contain members of reference type
  • Cannot inherit from other classes and cannot be used as a base class
  • Cannot contain virtual functions
  • Anonymous union can directly access union members in the scope where the definition is located
  • Anonymous union cannot contain protected members or private members
  • Global anonymous unions must be static

explicit

The explicit keyword in C++ can only be used to modify a class constructor with only one parameter. Its function is to indicate that the constructor is explicit, not implicit. Another keyword corresponding to it is implicit, meaning Is hidden, the class constructor is declared as implicit by default

  • When explicit decorates the constructor, it can prevent implicit conversion and copy initialization
  • When explicit decorates a conversion function, implicit conversions can be prevented, except for conversions by context

references & pointers

insert image description here

lvalue reference & rvalue reference

An lvalue is an expression representing data, such as a variable name, a dereferenced pointer variable. Generally, we can obtain its address and assign a value to it, but the lvalue modified by const cannot assign a value to it, but its address can still be obtained.
In general, an object whose address can be taken is an lvalue.

// 以下的a、p、*p、b都是左值
int a = 3;
int* p = &a;
*p;
const int b = 2;

An rvalue is also an expression representing data, such as: literal constant, expression return value, return value of a function by value (return by value, not return by reference), rvalue cannot appear on the left side of the assignment symbol and Unable to fetch address.
In general, objects that cannot be addressed are rvalues.

double x = 1.3, y = 3.8;
// 以下几个都是常见的右值
10;                 // 字面常量
x + y;             // 表达式返回值
fmin(x, y);        // 传值返回函数的返回值

An lvalue reference is a reference to an lvalue, aliasing the lvalue.

// 以下几个是对上面左值的左值引用
int& ra = a;
int*& rp = p;
int& r = *p;
const int& rb = b;

An rvalue reference is a reference to an rvalue, aliasing the rvalue.
The expression of rvalue reference is to add two & after the specific variable type name, for example: int&& rr = 4;.

// 以下几个是对上面右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

Summary of lvalue references:

  1. Lvalue references can only refer to lvalues, not rvalues ​​directly.
  2. But const lvalue references can refer to both lvalues ​​and rvalues.
// 1.左值引用只能引用左值
int t = 8;
int& rt1 = t;

//int& rt2 = 8;  // 编译报错,因为10是右值,不能直接引用右值

// 2.但是const左值引用既可以引用左值
const int& rt3 = t;

const int& rt4 = 8;  // 也可以引用右值
const double& r1 = x + y;
const double& r2 = fmin(x, y);

Q: Why can a const lvalue reference also refer to an rvalue?
Answer: Before the C++11 standard came into being, there was no concept of rvalue references. At that time, if you wanted a type that could receive both lvalues ​​and rvalues, you needed to use const lvalue references, such as push_back for standard containers. Interface: void push_back (const T& val).
In other words, if const lvalue references cannot refer to rvalues, some interfaces are not well supported.

Summary of rvalue references:

  1. Rvalue references can only refer to rvalues, not lvalues ​​directly.
  2. But an rvalue reference can refer to an lvalue that is moved.

move, this article refers to std::move (C++11), is used to force an lvalue to an rvalue to achieve move semantics.
After the lvalue is moved, it becomes an rvalue, so an rvalue reference can be referenced.

// 1.右值引用只能引用右值
int&& rr1 = 10;
double&& rr2 = x + y;
const double&& rr3 = x + y;

int t = 10;
//int&& rrt = t;  // 编译报错,不能直接引用左值


// 2.但是右值引用可以引用被move的左值
int&& rrt = std::move(t);
int*&& rr4 = std::move(p);
int&& rr5 = std::move(*p);
const int&& rr6 = std::move(b);

Significance of lvalue reference
Passing parameters by value and returning by value will generate copies, some even deep copies, which is very expensive. The actual significance of lvalue references is that both as parameters and as return values ​​can reduce copying, thereby improving efficiency.
Shortcomings of lvalue references
Although lvalue references perfectly solve most problems, some problems still cannot be solved well.
When the object still exists after the scope of the function, it can be returned by lvalue reference, which is no problem.
But when the object (the object is a local object in the function) does not exist after leaving the scope of the function, it cannot be returned using an lvalue reference.
Therefore, for the second case, the lvalue reference can't do anything, it can only return by value.
The significance of rvalue references
So, in order to solve the above-mentioned copy problem of returning by value, the C++11 standard adds rvalue references and move semantics.
Move semantics : Moving resources from one object to another (transfer of resource control).
Move construction : Transfers the resource of the parameter rvalue to construct itself.

In general, if these two functions are defined in the class, when constructing the object:
if the lvalue is used as a parameter, then the copy constructor will be called to make a copy (if it is in the heap space like string If there is a class with resources on it, then a deep copy will be made every time the copy construction is called).
If an rvalue is used as a parameter, then the move construction will be called, and calling the move construction will reduce copying (if it is a class like string that has resources on the heap space, then each time the move construction is called, one less deep copy will be made ).

Move assignment : transfer the resources of the rvalue of the parameter to assign to itself.

In general, if these two functions are defined in the class, when assigning an object:
if an lvalue is used as a parameter, then copy assignment will be called and a copy will be made (if it is in the heap like string If there are resource classes in the space, a deep copy will be made every time the copy assignment is called).
If an rvalue is used as a parameter, then move assignment will be called, and call move assignment will reduce copying (if it is a class like string that has resources on the heap space, then each call of move assignment will save one deep copy ).

Universal reference:
&& of a certain type represents an rvalue reference (for example: int&&, string&&),
but && in a function template does not represent an rvalue reference, but a universal reference. The template type must be determined by inference, and it will receive an lvalue is deduced as an lvalue reference, and will be deduced as an rvalue reference after receiving an rvalue.
However, in universal references, rvalue references will lose the properties of rvalue references.
Perfect forwarding
means that in a function template, parameters are passed to another function in the current function template completely according to the parameter type of the template.
Therefore, in order to achieve perfect forwarding, in addition to using universal references, we also need to use std::forward (C++11), which retains the original type properties of the object during the process of passing parameters.
In this way, the rvalue reference can maintain the properties of the rvalue during the transfer process.

void Func(int& x) {
    
     cout << "左值引用" << endl; }

void Func(const int& x) {
    
     cout << "const左值引用" << endl; }

void Func(int&& x) {
    
     cout << "右值引用" << endl; }

void Func(const int&& x) {
    
     cout << "const右值引用" << endl; }


template<typename T>
void PerfectForward(T&& t)  // 万能引用
{
    
    
	Func(std::forward<T>(t));  // 根据参数t的类型去匹配合适的重载函数
}

int main()
{
    
    
	int a = 4;  // 左值
	PerfectForward(a);

	const int b = 8;  // const左值
	PerfectForward(b);

	PerfectForward(10); // 10是右值

	const int c = 13;
	PerfectForward(std::move(c));  // const左值被move后变成const右值

	return 0;
}

insert image description here
In addition to the above usage scenarios, the relevant interface functions of the C++11 standard STL container also achieve perfect forwarding, so that the value of rvalue references can be truly realized.
For example, the container list in the STL library

Summary
Rvalue references make C++ programs run more efficiently.

reference blog post

The introduction of reference operations in C++ ensures the safety and convenience of reference use while adding more restrictions on the use of references, and can also maintain the elegance of the code. Use appropriate operations in appropriate situations, and the use of references can avoid the situation of "pointers flying all over the sky" to a certain extent, and it also has a certain positive significance for improving program stability. Finally, the underlying implementations of pointers and references are the same, so there is no need to worry about the performance gap between the two.

Guess you like

Origin blog.csdn.net/weixin_45184581/article/details/129491269