Class 1: objects and memory 1.1 view objects through memory
Let's first review the definition of classes and objects. Classes are blueprints or prototypes that define all instance variables and methods of the same class. objects are class instantiation. From the memory perspective, we can understand these two definitions in this way. Classes depict the memory layout of instances, determine the location and size of each data member in the instance in a continuous memory and the memory interpretation method. The object is the memory allocated by the system according to the memory layout described by the class. In addition to instance variables and methods, classes can also define class variables and class methods. This is what we call static variables and static functions. They do not belong to a specific object, but to the entire class, therefore, the object memory layout and memory size are not affected. Through the above discussion, we can know that the object is essentially a continuous memory, and the object type (class) is the interpretation of this memory. In C ++, we can use four types of conversion operators to change the object type. This conversion changes the memory interpretation mode without modifying the value in the memory. The valid way to modify the object memory value is to modify the object data member through the member function/youyuan function. Modifying object values through member functions is a mechanism in C ++ to ensure object security. However, this mechanism is not mandatory, you can avoid this mechanism by brute force means (for example, you can obtain the starting address of an object and modify the memory value based on the memory layout of the object), except for the extremely special circumstances, such illegal means should be banned, because such violent code is difficult to understand, inconvenient to transplant, and prone to errors; in addition, during the running process of the program, some defects in the code will also illegally modify the object memory value, which is the root cause of many difficult bugs in our program. Therefore, correct writing of classes, understanding the running characteristics of objects in the memory, and reasonable control of object creation and destruction are the basic guarantee for stable operation of a program.
1.2 objects in different memory regions
In C ++, objects are usually stored in three memory areas: Stack, stack, and global/static data areas. Correspondingly, objects in these three regions are called stack objects, heap objects, and global/static objects.
Global/static data zone: Global Objects and static objects are stored in this zone. objects in this memory zone are released only after they are created until the process ends. It can be accessed by multiple threads during its lifetime. It can be used as a method for multi-thread communication. Therefore, thread security should be considered for global and static objects, especially for local static variables in functions, it is easy to forget its thread security. Global Objects and some static objects have one feature: the initialization operation of these objects is prior to the execution of the main function, and the initialization sequence of these objects (which may be distributed in different source files) is not specified, therefore, do not start threads during their initialization, and their initialization operations should not have dependencies.
Heap: the heap object is an object that dynamically allocates memory in the heap through new/malloc and releases memory through delete/free. We can precisely control the creation and destruction of such objects. Heap objects are widely used in C ++. Heap objects can be used for object sharing between different threads and functions. Heap objects are also used for large objects (stack space is limited ), in particular, virtual function polymorphism is generally implemented by heap objects. Using Heap objects also has some disadvantages: 1. the programmer needs to manage the Life Cycle. If he forgets to release the program, there will be Memory leakage. Multiple releases may cause program crashes. This problem can be avoided through smart pointers. 2. the time efficiency and space efficiency of heap objects are not as high as those of stack objects. Heap objects generally use a certain search algorithm to find the appropriate memory size in the heap, which is time-consuming, in addition, the size of memory allocated from the heap is several bytes larger than the actually applied memory, which is space-consuming, especially for small objects. 3. frequent use of new/delete heap objects can cause a large amount of memory fragments and insufficient memory usage. Two or three problems can be solved through memory allocation once and multiple usage. A better way is to implement a specific memory pool based on business characteristics.
STACK: stack objects are self-generated and self-destroyed objects. programmers do not need to manage their lifecycles. Generally, temporary objects and local objects in functions are stack objects. Stack objects are efficient because they do not need to perform memory searches and only move the top pointer of the stack. In addition, stack objects are thread-safe because different threads have their own stack memory. Of course, the stack space is limited, so it is necessary to prevent Stack Overflow during use. Usually large objects, large arrays, and recursive functions must use stack objects with caution.
2 C ++ object creation and destruction
The C ++ class has four basic functions: constructor, destructor, copy constructor, and assign value operator overload function. These four functions manage the creation and destruction of C ++ objects, correct and complete implementation of these functions is essential for the secure operation of C ++ objects.
2.1 Structure/Analysis
There are two steps to create an object: 1. Allocate sizeof (canytype) bytes of memory in the memory; 2. Call the appropriate constructor to initialize the allocated memory. The size of the C ++ object is determined by three factors: 1. size of each data member; 2. size of the padding Space Generated by byte alignment; 3. to support a pointer added to the virtual mechanism compiler, the size is four bytes. There are two types of virtual mechanism pointers: 1. supports virtual table pointers of virtual functions. supports virtual inheritance of virtual base class pointers. During virtual inheritance, the derived class only saves one inherited entity of the base class. For example, in the diamond inheritance relationship in the following example, Class D contains only one entity of Class. In addition, Class A is an empty class, but the size of sizeof (a) is not 0, but 1, because this byte is required to uniquely identify different objects in the memory of Class.
Class {};
Class B: virtual public {};
Class C: virtual public {};
Class D: Public B, public c {}
As discussed above, apart from data members written by programmers, the class sometimes has some members secretly added to you by the compiler to support virtual mechanisms, these members are not directly used in the code, but may be illegally modified by our code. For example, an improper constructor modifies the value of the virtual mechanism pointer. When writing constructor, we often use the following code to initialize the entire object:
Memset (this, 0, sizeof (* This ));
This initialization method can be used only when the class does not involve the virtual mechanism. Otherwise, it modifies the virtual mechanism pointer and makes the behavior of the Class Object undefined.
There are two steps to destroy an object: 1. Call the Destructor; 2. Return the memory to the system. The role of the Destructor is to release the resources requested by the object. The Destructor is usually automatically called by the system. In the following situations, the system calls the Destructor: 1. end of stack object lifecycle: including exit scope, normal return of function (nrv optimization not considered), and function exception throw; 2. when the heap object is deleted; 3. the global object is at the end of the process. The Destructor must be explicitly called only in one situation, that is, the object built with placement new. When the class contains virtual functions, we should declare the virtual destructor. The role of the virtual destructor is: when you delete a base class pointer to a derived class object, ensure that the destructor of the derived class is called correctly. Many resource leaks are caused by incorrect use of virtual destructor. There are two types of resource leaks: 1. resources directly allocated in the derived class; 2. the resources allocated by the member objects in the derived class. In particular, the second type is highly concealed.
Constructor and constructor are a group of functions called in pairs. In particular, for stack objects, the call is automatically completed by the system, therefore, we can use this feature to encapsulate the operations that need to appear in pairs in the constructor and destructor, which are automatically completed by the system, in this way, you can avoid forgetting to perform certain operations due to program omissions. For example, the application and release of resources, the lock and unlock in multiple threads can be automatically managed using this feature of stack objects.
2.2 copy/assign values
Copy constructor and assign value operator overload function are a pair of twins. Generally, if a class needs to explicitly write a copy constructor, it also needs to explicitly write a value assignment operator overload function. The copy constructor function constructs a new object with an existing object. The function of the value assignment operator overload function is to replace an existing object with an existing object. Let's look at the following statements:
String str1 = "string test"; // call a constructor with Parameters
String str2 (str1); // call the copy constructor.
String str3 = str1; // call the copy constructor.
String str4; // call the default constructor
Str4 = str3; // call the value assignment operator to overload the function.
The following is a prototype of a copy constructor or a value assignment operator overload function:
Class string
{
PRIVATE:
Char * m_pstr;
Int m_nsize;
Public:
String (); // default constructor
String (const char * pstr); // constructor with Parameters
~ String ();
String (const string & cother );//Copy constructor
String & operator = (const string & cother );//Value assignment operator overload Function
}
The parameter types of these two functions are const string &. We know that C ++ objects are usually referenced as function parameters, which improves the parameter transfer efficiency, when an object is used as a function parameter, the copy constructor is called to generate a temporary object for the function, which is less efficient. A copy constructor is a special function. For other functions, using objects as function parameters is at most a loss of efficiency, however, using an object as a function parameter for the copy constructor forms an infinite recursive call. Therefore, the copy constructor must use a common reference as a parameter.
The copy constructor has a default implementation in the C ++ compiler. The implementation method is to copy the memory by bit (memcpy (this, & cother, sizeof (string )), if the default implementation meets our requirements, we do not need to explicitly implement this function; otherwise, we must implement it, the basis for determining whether the bitcopy semantics is met is whether other resources need to be dynamically allocated in the member data of the class, such as the string class above, the member m_pstr needs to allocate memory from the heap to store specific strings. The heap memory is not properly managed by the bit copy semantics, therefore, when copying or assigning values to a string object, the programmer needs to manage the memory. The copy constructor is usually called in three cases. an object is passed in a function as a value; 2. an object is returned from the function by passing values. 3. an object needs to be initialized through another object. If you make sure that the usage of class objects does not meet the above three conditions, it means that you do not need to copy the constructor at all, and directly privatize the copy constructor is the safest choice. From the above discussion, we know that there are three processing policies for the copy constructor (the same applies to the value assignment operator overload function): 1. do not write anything, which is processed by default; 2. explicit write copy structure; 3. privatize the copy structure. Before writing a class, we must analyze the implementation method of the class and the usage method of the class object, and select a policy explicitly, if you give up your choice, you will find a foreshadowing for future bugs.
The selection policy of the copy/assign value function is discussed above. Let's take a look at their specific implementation methods. The copy constructor function constructs a new object by an object. Only one copy operation can be completed. The function of the value assignment operator overload function replaces an existing object with an object. To complete this function, three operations are required: Self-Assignment Check, clear original object, and copy new object. For example, the implementation of the string class:
Class string
{
PRIVATE:
Char * m_pstr;
Int m_nsize;
Public:
String (); // default Structure
String (const char * pstr); // constructor with Parameters
~ String ();
String (const string & cother) // implements the copy constructor.
{
Copy (cother );
}
String & operator = (const string & cother) // implements the value assignment operator to overload the function.
{
If (this! = & Cother)
{
Clear ();
Copy (cother );
}
Return * this;
}
PRIVATE:
Void copy (const string & cother)
{
M_pstr = new char [cothre. m_nsize + 1];
Strcpy (m_pstr, cother. m_pstr );
M_nsize = cother. m_nsize;
}
Void clear ()
{
If (m_pstr! = NULL)
{
Delete [] m_pstr;
M_pstr = NULL;
}
M_nsize = 0;
}
}
The implementation modes of these two functions in the string class can be directly applied in other classes, you only need to change the copy and clear () functions.
3. Conclusion
C ++ programmers deal with classes, objects, and memory every day. It is not difficult to write a class to implement a function, however, it is not easy to implement a robust, reusable, and scalable class. Most of the time we write a class, we still use C thinking. We do not have to consider the four basic functions of the class carefully enough to understand the features of class objects running in different memory areas, it is easy to generate some low-level bugs, and it also brings difficulties to the subsequent code maintenance and expansion. This article gives a basic introduction to these contents and hopes to help you.