Re-learn C++ (ten) classes and dynamic memory allocation


This chapter will introduce how to use new and delete for classes and how to deal with some subtle problems caused by the use of dynamic memory.

1. Dynamic memory and classes

C++ uses new and delete operators to dynamically control memory, but using these operators in a class will cause many new programming problems. In this case, the destructor will be necessary. Sometimes, the assignment operator must be refactored to ensure that the program runs.

1.1 Special member functions

C++ automatically provides the following member functions:

  • Default constructor, if no constructor is defined;
  • The default destructor, if not defined;
  • Copy constructor, if not defined;
  • Assignment operator, if not defined;
  • Address operator, if not defined.

C++11 provides two other special member functions: move constructor and move assignment operator, which will be discussed in C++primer Chapter 18.

(1) The default constructor

If no constructor is provided, C++ will create a default constructor, but once there is a constructor, it will no longer provide a default constructor. The default constructor form is as follows.

Klunk::Klunk(){
    
     };

Then we can define the object by default constructor:

Klunk lunk;//调用默认构造函数

The parameterized constructor can also be the default constructor, as long as all parameters have default values. In this case, you can no longer define a constructor without parameters, because it will cause ambiguity. The default constructor with parameters has the following form:

Klunk(int n = 0){
    
    klunk_ct = n;}

(2) Copy constructor

The copy constructor is used to copy an object to a newly created object. It is used in the initialization process, not in the regular assignment process. Its function prototype is usually as follows:

Class_name(const Class_name &);

Need to know when to call and what functions.

(3) When to call the copy constructor

When creating a new object and initializing it to an existing object of the same type, the copy constructor will be called . Assuming motto is a StringBad object, the copy constructor will be called in the following four cases:

StringBad ditto(motto);//calls StringBad( const StringBad &)
StringBad metoo = motto;//calls StringBad( const StringBad &)
StringBad also = StringBad(motto);//calls StringBad( const StringBad &)
StringBad * pStringBad = new StringBad(motto);//calls StringBad( const StringBad &)

The middle two declarations may use the copy constructor to directly create metoo and also, or use the copy constructor to generate temporary objects, and then assign the contents of the temporary objects to metto and also, depending on the specific implementation. The last type of declaration uses motto to initialize an anonymous object and assign the address of the new object to the pStringBad pointer.

Whenever the program makes a copy of an object, the compiler will use the copy constructor. For example, when a function passes an object by value, or returns an object, the copy constructor will be used. For another example, when three Vector objects are added, the compiler may (varies by compiler) generate temporary Vector objects to save the intermediate results.

Since passing objects by value will call the copy constructor, objects should be passed by reference. Save time and space to store new objects.

(4) The function of the default copy constructor

The default copy constructor copies non-static members one by one (member copy is also called shallow copy), and it copies the value of the member .

If it is a general type copy value is not worth mentioning, but if a pointer similar to char* str; is defined in the class, its value is the address! Especially if two objects are defined, the destructor will be called twice at the end of the program. If the pointer is deleted by the destructor, the pointer will be deleted twice, which may cause the program to terminate abnormally.

(5) Define an explicit copy constructor to solve the problem (deep copy)

The solution to this problem in class design is to perform deep copy. In other words, each object has its own string, not a string that refers to another object. You can write StringBad's copy constructor like this.

StringBad::Stringbad( const StringBad& st)
{
    
    
	len = st.len;
	str = new char [len + 1];//新地址
	std::strcpy(str, st.str);//将对象的字符串复制给新对象,而不是指针复制!!!
}

The reason why the copy constructor must be defined is that some class members are pointers to data initialized with new, not the data itself.

If the class contains pointer members initialized with new, a copy constructor should be defined to copy the pointed data instead of the pointer. This is called deep copy . Another form of copy (member copy or shallow copy ) is just to copy the pointer value. Shallow copy only superficially copies pointer information.

(6) Assignment operator

C++ allows assignment of class objects, which is achieved by automatically overloading the copy operator for the class . Its prototype is:

Class_name& operator= (const Class_name &);

When assigning an existing object to another object, the overloaded assignment operator will be used.

StringBad headlin1("Celery Stalks");
StringBad kont;
knot = headline1;//调用赋值运算符

When initializing an object, the assignment operator is not necessarily used. It calls the copy constructor:

StringBad metoo = knot;

This type of = assignment may also be performed like this: first use the copy constructor to create a temporary object, and then assign the value of the temporary object to the new object through assignment. In other words, initialization always calls the copy constructor, and the assignment operator may also be called when the = operator is used.

Similar to the copy constructor, the implicit implementation of the assignment operator is a member copy operation , but static data members are not affected. If the member itself is a class object, the program will use the assignment operator defined for this class to copy the member.

Assignment operator is the same as the problem of copy constructor, which belongs to shallow copy, so in order to solve the problem of pointers, it is necessary to provide the definition of assignment operator (deep copy). But there are some differences:

  • Since the target object may reference previously allocated data, the function should use delete[] to release these data.
  • The function should avoid assigning the object to itself; otherwise, before reassigning the object, the memory release operation may delete the content of the object.
  • The function returns a reference to the calling object.

Write an assignment operator for the StringBad class:

StringBad& StringBad::operator = (cosnt StringBad& st)
{
    
    
	if(this == &st)
		return *this;//避免赋值给自己
	delete [] str;//删除以前的值
	len = st.len;
	str = new char [len + 1];
	std::strcpy(str, st.str);//复制字符串
	return *this;
}

2. Improved new String class

The String class should contain all the functions of the standard string function cstring. Here we only introduce the working principle of the String part (the String class is just an example for illustration, while the C++ standard string class has a lot of content):

int length() const{
    
    return len;}//和size()一样
friend bool operator<(const String &st1, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st1, const String &st2);
friend operator>>(istream& is, String &st);
char& operator[] (int i);
const char& operator[] (int i) const;
static int HowMany();

The specific implementation method will not be described in detail here, if you need to understand, please refer to chapter 12.2 of the book

3. Things to pay attention to when using new in the constructor

We know that special care must be taken when using new to initialize pointer members of an object.

  • If you use new in the constructor to initialize pointer members, you should use delete in the destructor.
  • new and delete must be compatible with each other. new corresponds to delete, new[] corresponds to delete[].
  • If there are multiple constructors, you must use new in the same way, either with parentheses or without. Because there is only one destructor.
  • A copy constructor should be defined to initialize one object to another through deep copy.
  • An assignment operator should be defined to assign one object to another through deep assignment.

NULL, 0, or nullptr:

  • In the past, the null pointer can be represented by 0 or NULL (in many header files, NULL is a symbolic constant defined as 0).
  • C programmers usually use NULL instead of 0 to indicate that this is a pointer, just like using'\0' instead of 0 to indicate a null character to indicate that this is a character.
  • C++ traditionally prefers to use a simple 0 instead of the equivalent NULL.
  • C++11 provides the keyword nullptr.

If the class member is a string type, the copy or assignment is similar to ordinary variable copy or assignment, because it uses the copy constructor and assignment operator defined by the member type.

4. Description of the returned object

When a member function or a regular function returns an object, there are several return methods to choose from: return a reference to the object, a const reference to the object, or a const object. The first two have been introduced so far, and the third has not been introduced yet.

4.1 Return a reference to a const object

The common reason for using const references is to improve efficiency. such as

//第一种情况
Vector Max(const Vector & v1, const Vector& v2)
{
    
    
	if(v1.magval() > v2.magval() )
		return v1;
	else
		return v2;
}
//第二种情况
const Vector&  Max(const Vector & v1, const Vector& v2)
{
    
    
	if(v1.magval() > v2.magval() )
		return v1;
	else
		return v2;
}

The above two methods are fine, but the second method of returning a reference is more efficient. The first way to return an object is to call the copy constructor, while returning a reference does not.

But note:

  • First: The object pointed to by the return should exist when the calling function is executed.
  • Second: v1 and v2 are both declared as const references, so the return type must be const in order to match.

4.2 Return a reference to a non-const object

Two common situations where non-const objects are returned:

  • First: overloading the assignment operator. To allow continuous assignment.
  • Second: the << operator used with cout. In order to be able to output continuously.

Both use references to avoid calling the copy constructor to create a new String object.

4.3 Return object

If the returned object is a local variable in the called function, it should not be returned by reference, because the local object will call the destructor when the called function is completed. Therefore, when control returns to the calling function, the object pointed to by the reference will no longer exist.

The overloaded arithmetic operator outputs this situation, such as adding two objects, and a temporary object needs to be created. This overhead is inevitable.

4.4 return const object

The above arithmetic operators, such as addition, will produce temporary objects, so multiple addition operations (obj1 + obj2 + obj3) can be used. In this case, the following expressions will become legal:

obj1 + obj2 = obj3;//加法产生临时对象

If you are worried that this will happen, there is a simple solution that is to declare the return type as const. Such as const Vector::operator.

5. Use pointers to objects

5.1 Talk about new and delete again

The destructor will be called in the following cases

  • If the object is a dynamic variable (such as a temporary variable), when the program block defining the object is executed, the destructor of the object will be called.
  • If the object is a static variable, the destructor of the object will be called at the end of the program.
  • If the object is created with new, its destructor will only be called when you explicitly delete the object with delete.

5.2 Summary of pointers and objects

When using object pointers, there are a few things to note:

  • Use conventional notation to declare a pointer to an object:
    string *glamour;
  • You can initialize the pointer to point to an existing object:
    string *first = &sayings[0];
  • You can use new to initialize the pointer, which will create a new object:
    string *favorite = new string(sayings[choice]);
  • Using new on the class will call the corresponding class constructor to initialize the newly created object:
    string *gleep = new string;
    string *glop = new string("hello hello");
    string *favorite = new string(sayings[choice]) ;
  • You can use the -> operator to access class methods through pointers:
    favorite -> length();
  • You can apply the dereference operator (*) to the object pointer to get the object:
    if(sayings[i] <*first)//Compare two strings
    first = &saying{i};

6. Simulation queue

The queue is (FIFO, first-in, first-out), we create a Queue class (the standard class is queue). Here we write an ATM automatic withdrawal simulation queue program, the main tasks are: design a customer class; write a program to simulate the interaction between the customer and the queue.

If the constructor is set as private, then the following methods are not allowed (in addition, another method is introduced in Chapter 18 of the textbook, adding the keyword delete):

Queue snick(nip);//不允许
tuck = nip;//不允许

First, the above method avoids the automatically generated default method definition. Second, because these methods are private, they cannot be widely used.


Overview Catalog
Previous: (9) Using Classes
Next: (11) Class Inheritance


Article reference: "C++ Primer Plus Sixth Edition"

Guess you like

Origin blog.csdn.net/QLeelq/article/details/112599895