C + + classes and memory management [C++ primer notes]

Program construction

Design a badstring class that will contain a string pointer and a value representing the length of the string.

badstring.h

#include <iostream>
#ifndef BADSTRING_H_
#define BADSTRING_H_

class badstring
{
private:
    char *str;
    int len;
    static int num_strings;

public:
    badstring(const char *s);
    badstring();
    ~badstring();
    friend std::ostream &operator<<(std::ostream &os, const badstring &st);
};
#endif


  • char *str uses a string pointer instead of a char array to represent the contents of a string, which means that the class declaration does not allocate memory space for the string itself.
  • num_strings ensures that each time a new object is created, the value of the shared variable is + 1, recording the total number of badstring objects. In addition, the destructor will share the value of the variable - 1.

badstring.cpp

#include <cstring>
#include "badstring.h"

using std::cout;
int badstring ::num_strings = 0;

badstring::badstring(const char *s)
{
    int len = std::strlen(s);
    str = new char[len + 1];
    std::strcpy(str, s);
    num_strings++;
    cout << num_strings << ": \"" << str << "\""
         << "  object create\n";
}
badstring::badstring()
{

    int len = 4;
    str = new char[4];
    std::strcpy(str, "c++");
    num_strings++;
    cout << num_strings << ": \"" << str << "\"" << str << " default object create\n";
}
badstring::~badstring()
{
    cout << str << "  object deleted  ,";
    --num_strings;
    cout << num_strings << " words left\n";
    delete[] str;
}

std::ostream &operator<<(std::ostream &os, const badstring &st)
{
    os << st.str;
    return os;
}

The static member num_ The value of strings is initialized to 0.
Static member variables cannot be initialized when a class is declared because the declaration only describes how to allocate memory, but does not allocate memory.

main.cpp

#include <iostream>

using std::cout;
#include "badstring.h"

void callme(badstring &st1);
void callme2(badstring st2);
int main()
{

    using std::endl;
    {
        cout << "Start an inner block" << endl;
        badstring s1("I am string 1");
        badstring s2("I am strnig 2");
        badstring s3("I am string 3");
        cout << "s1:" << s1 << endl;
        cout << "s2:" << s2 << endl;
        cout << "s3:" << s3 << endl;
        callme(s1);
        cout << "s1:" << s1 << endl;
        //The copy constructor is called as a function of the object passed by value.
        callme2(s2);
        cout << "s2:" << s2 << endl;

        cout << "initalize one object to another\n";
        badstring s4 = s3;
        cout << "s4 :  " << s4 << endl;
        cout << "Assign one object to another :" << endl;
        badstring s5;
        s5 = s1;
        cout << "s5: " << s5 << endl;
        cout << "exiting the block..." << endl;
    }
    return 0;
}

void callme(badstring &str1)
{
    cout << "String passed by reference:  ";
    cout << "   \"" << str1 << " \"" << std::endl;
}

void callme2(badstring sb)
{
    cout << "String passed by value:\n";
    cout << "   \"" << sb << " \"" << std::endl;
} 

Compilation and running results:

 ✘ wingchi  ~/Cpp/cppPrimer  g++ -c  vegnews.cpp badstring.cpp 
 wingchi@wingchi01  ~/Cpp/cppPrimer  g++ -c  vegnews.cpp badstring.cpp 
 wingchi@wingchi01  ~/Cpp/cppPrimer  g++ badstring.o vegnews.o -o main 
 wingchi@wingchi01  ~/Cpp/cppPrimer  ./main                            
Start an inner block
1: "I am string 1"  object create
2: "I am strnig 2"  object create
3: "I am string 3"  object create
s1:I am string 1
s2:I am strnig 2
s3:I am string 3
String passed by reference:     "I am string 1 "
s1:I am string 1
String passed by value:
   "I am strnig 2 "
I am strnig 2  object deleted  ,2 words left 
s2:
initalize one object to another
s4 :  I am string 3
Assign one object to another :
3: "c++"c++ default object create
s5: I am string 1
exiting the block...
I am string 1  object deleted  ,2 words left
I am string 3  object deleted  ,1 words left
����U  object deleted  ,0 words left
free(): double free detected in tcache 2
[1]    5742 abort (core dumped)  ./main

You can see that the program crashed at the end of execution, and unimaginable results appeared in the process of execution.

Program description

There is no problem creating s1 ~ s3 and printing,
Then callme and callme2 functions are called. The former is passed by reference and the latter is passed by value. Next, we have an unexpected first result:

String passed by value:
   "I am strnig 2 "
I am strnig 2  object deleted  ,2 words left 
s2:

After string2 calls the callme2 function, it is revoked (the destructor is executed)

Next, we create s4 and s5 strings and initialize them in this way:

        badstring s4 = s3;
        badstring s5;
        s5 = s1;

Finally, we end the function, and each object calls its own destructor. The results are as follows:

exiting the block...
I am string 1  object deleted  ,2 words left
I am string 3  object deleted  ,1 words left
����U  object deleted  ,0 words left
free(): double free detected in tcache 2

Since the automatic storage object is deleted in the reverse order of creation, s5 (s1), s4 (s3) and s3 are deleted first
You can see that it is normal to delete s5 and s4. When deleting s3, an exception occurs.

Result analysis

Let's analyze from the code with the first strange results:

 callme2(s2);
//String passed by value:
//  "I am strnig 2 "
//I am strnig 2  object deleted  ,2 words left

People familiar with C language should know that the parameters in C language, whether passing values or passing addresses (pointers), use the copy mechanism. (that is, copy an argument to the function.)

To explain this phenomenon, we first need to understand the copy constructor of C + +

The copy constructor is used to copy an object into a newly created object. If this function is not defined in the class, it will be automatically generated by the compiler. It is the default copy constructor. It is used for the initialization process. The prototype is as follows:
Class_name (const Class_name &)
badstring (const badstring &)
For copy constructors, you need to know two things: when to call and what to do:

When to call:
When you create a new object and initialize it into an existing object of the same type, the copy constructor will be called:

Suppose there is already an object: badstring s0("I am the original string"); It is possible for the following four declarations to call the copy constructor:

badstring s1(s0);
badstring s2 = s0 ;
badstring s3 = badstring(s0) ;
badstring *ps4 = new badstring(s0); 

The middle two declarations may use the copy constructor to create s2 and s3, or they may use a temporary object and then assign the contents of the temporary object to s2 and s3
The last declaration initializes an anonymous object with s0 and assigns the address of the new object to the pstring pointer.

Whenever a program produces a copy of an object, the compiler uses the copy constructor. Specifically, when a function passes an object by value or a function returns an object, the copy constructor is used.

Functions of the default copy constructor
The default copy constructor copies non static members one by one. It copies the values of members (also known as shallow copy). Static variables are not affected because they belong to the whole class.

From here, we know that calling the callme2 function above will result in the creation of a new object. When the function exits, the temporary object will be destroyed, which triggers the destructor.
At the same time, because the default copy constructor is a shallow copy, it assumes such a process when copying the str variable of s2:

temp.str = s2.str ;

What is copied here is not a string, but a pointer to the string. That is, the str member of the temporary object and the str member of S2 save pointers to the same address. When the destructor is called, it frees the memory of the temporary variable, which frees the memory of s2.str

Note: the default copy constructor does not explain its behavior, so it does not print the creation statement, nor does it add the technocrator strings_ The value of num. But the destructor updates the count because any object will be called.

cout << "initalize one object to another\n";
    badstring s4 = s3;

Note that the above assignment operation is also one of the four declarations we mentioned, that is, a s4 object is also created here using the default copy constructor
Remember, this initialization statement is equivalent to the following statement:

badstring s4 = badstring(s3);
    cout << "Assign one object to another :" << endl;
        badstring s5;
        s5 = s1;
//Assign one object to another :
//3: "c++"c++ default object create

You can see that the above assignment statement causes the default constructor to be called. What is the reason here? Let's move on to another concept: assignment operators

Assignment Operators

C + + allows class objects to assign values, which is realized by automatically overloading and copying operators for classes. The prototype of this operator is:

Class_name &Class_name::operator=(const Class_name &)

It accepts and returns a reference to a class object. For example, the assignment operator of badstring class is as follows:

badstring& badstring::operator=(const badstring &)

Use of assignment operators:

badstring s1 ("haha I am a string") ;

badstring s2 ;

 s2 = s1 ; // assignment operator invoke 

Assignment operators are not necessarily used when initializing objects

badstring s3 =s1 ; // Use the copy constructor, or the assignment operator

Like the default copy constructor, non static members are copied one by one (also known as shallow copy), and the values of members are copied. Static members are not affected because they belong to classes.

Tags: C++

Posted on Sat, 23 Oct 2021 21:14:55 -0400 by yoost