Don't be fooled by the C++ of auto generation rule

  • 2020-04-01 21:34:37
  • OfStack

C++ objects can be created in two ways: constructors and copy constructors. Suppose we define class A and use it to create objects.


A a,b; 
A c=a; 
A d(b);

Objects a and b are created using the default constructor a :: a () provided by the compiler, which we call the definition of the object (which contains the meaning of the declaration). Objects c and d are created using existing objects using the copy constructor A::A(const A&) provided by the compiler, which we call the initialization of the object (which contains the definition and declaration).

Many people may confuse object initialization with object assignment, for example.


c=d; 

Assigning object d to object c here does not create a new object; it does not call any constructors. This statement is supported by the assignment operator overload function const A&operator=(const A&) provided by the compiler by default.

In addition to providing default constructors, copy constructors, and assignment operator overloads, the compiler probably provides us with A destructor A::~A(), but the destructor here is not virtual (some children will forget this).

This basic syntax is probably not new to anyone who has studied C++, and we've always known that compilers provide this convenience since we learned object-oriented C++. After years of programming practice and experience, we're absolutely convinced that the compiler does the work for us because we haven't had any problems. Even if I define an empty class (there is nothing in the class), the compiler will still "obediently" generate the above four functions for us.

If you have, congratulations, you have mastered the basic rules of C++. And unfortunately you and I both see the tip of the iceberg, the compiler doesn't work the way we use it. The reader may wonder, doesn't the compiler generate these functions? A: it depends on the definition of your class. So how exactly does the compiler generate these functions? Curious people like me want to find out, and this content is explained thoroughly in Inside The C++ Object Model. The author also through the way of "borrowing flowers to Buddha" will describe the object structure of the book with personal understanding and share.

Let's start with the simplest, does the compiler generate constructors for classes? If, in the example described above, there is only an empty class definition, we can say for sure that there is none. We shouldn't be surprised if the compiler does this. Imagine an empty class -- no data members, no member functions, even if the constructor is generated? Even if it is generated, it is just an empty constructor.


A(){} 

It can do nothing, it doesn't have to do anything. More "tragic" is that it not only has no positive significance, but also adds a completely unnecessary burden of function calls to compilers and programs.

In that case, let's make the class a little more complicated by adding data members and member functions, such as the code below (which we'll call example 1).


class A 
{ 
public: 
int var; 
void fun(){} 
};

Even so, the result is the same as above, no constructor! Because there's no reason to initialize var, and the compiler doesn't know what value to initialize it with.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050230.jpg ">

Sure enough, after object a is defined in the main function, no constructor is called.

Student: someone might say that 0 doesn't work? This is just our "wishful thinking". The value of an uninitialized variable itself can be indeterminate, so why generate a meaningless statement initialized to 0.

How exactly does the compiler generate constructors? ! Maybe you're just as mad as I am. It's not time to despair, though, because the compiler needs us to give it a "good reason" to generate a constructor. There are four good reasons why the compiler has to generate constructors.

First, let's change the type of var. This assumes that it is not a built-in type int, but a defined class B.

B var.

Can changing the type of a data member to a custom type affect the compiler's decision? A: probably. It depends on whether class B defines a constructor. The reader might get A little bit of A sense, yes, if B doesn't define A constructor (like here's A), then the compiler still has no reason to generate A constructor -- what does it initialize for B? Conversely, once B defines the default constructor B::B(), even if it is empty, the compiler has to create the default constructor for A (regardless of compiler depth optimization). Because an object of A needs to initialize its own member var with the default constructor of B, even though the constructor of B does nothing. Because the compiler cannot assume what the constructor of B does (to the extreme: what if a global variable is modified?) Therefore, it is absolutely necessary for the compiler to generate the constructor of A to ensure that the constructor of the data member of type B executes normally.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050231.jpg ">

Going to the constructor generated by the compiler for A, we find the statement (selected line) where the constructor of B is called.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050232.jpg ">

Of course, if B provides a constructor, but not the default constructor, then the programmer must step in to initialize the var, or the compiler will be all right -- error!

Therefore, the first valid reason for the compiler to generate a default constructor is -- The data member within the class is an object, and the class of that object provides a default constructor .

Now, let's go back to example 1, where instead of changing the type of var, we let A inherit from another class C.


class A:public C

We all know that in the C++ constructor initialization syntax, the constructor initializes the base class C before initializing its own data members or objects. Therefore, the problem here is similar to that of object member var. If the base class C does not provide any constructors, the compiler still does not provide the default constructor for A. If C provides a default constructor, the result is similar to the previous one.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050233.jpg ">

As expected, the compiler generates A constructor for A and calls the default constructor defined by the base class C. Likewise, if C does not provide a default constructor and other constructors are provided, the compilation will not pass.

This is also the second reason the compiler generates a default constructor -- The base class of the class provides the default constructor .

Let's go back to example 1 again, and this time we'll modify the member function fun.


virtual void fun(){} 

Let's change the member function of class A, fun, to A virtual function and again see if the default constructor is generated.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050234.jpg ">

This time the compiler unceremoniously generates the default constructor for A, even though it doesn't call any other constructors! What's the reason? It turns out that C++, in order to implement the polymorphic mechanism, needs to maintain a vftable for the class, and each object of this class holds a pointer to the virtual function table (generally stored in the first four four sections of the object, the implementation of polymorphic mechanism is not described here). The compiler generates A constructor for A, nothing more than to ensure that all objects it defines properly initialize Pointers to the VFPTR!

Okay, so here's the third reason the compiler generates the default constructor -- Virtual functions are defined in the class . There may be a more complex case here: the virtual function is not defined in the class itself, but it inherits the virtual function of the base class. Following the above principle, it follows that since the base class defines the virtual function, the base class itself needs to generate the default constructor to initialize its own virtual function table pointer. Once the base class produces the default constructor, the derived class needs to produce the default constructor to call it. At the same time, the derived class initializes the virtual function table pointer once in the generated default constructor if the reader is aware of the polymorphic mechanism.

Finally, we return to example 1 again, this time still having A inherit from C, but this time C is an empty class -- nothing, and the default constructor is not automatically generated. But the way A inherits C is going to change.


class A:public virtual C

A virtual inherits from c. what's different this time?

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201301/2013011316050235.jpg ">

This time the compiler also generates the constructor for A, and the initialization process is somewhat similar to that used for virtual functions. On closer inspection, the constructor also initializes a table, vbtable. For those familiar with the virtual inheritance mechanism, this table is called the virtual base class table, which records the offset positions of all the virtual base class subobjects that the class inherits within the objects defined by this class (we will discuss the implementation of the virtual inheritance mechanism in more detail later). To ensure that the virtual inheritance mechanism works correctly, an object must maintain a pointer to the table during initialization, called a virtual table pointer (VBPTR). The reason the compiler provides A default constructor for A is similar to that used for virtual functions.

This brings us to the fourth justification for the compiler to generate the default constructor Class USES virtual inheritance .

At this point, we have explained the reason why the compiler generates the default constructor for the class, and I believe you should have a general understanding of the constructor generation timing. These four "good reasons" are The reasons The compiler has to generate a default constructor for a class, called nontrival in Inside The C++ Object Model. In addition to these four cases, the compiler is called trival, which means there is no need to generate a default constructor for the class. The construction-generation criteria discussed here are written into the C++Standard, so it seems that standards are a set of criteria that "fit with normal thinking" (simply YY), which is what they are, and that compilers should not do unnecessary work for consistency.

Through the discussion of the default constructor, it is believed that the generation time of copy constructor, assignment operator overload function, destructor should be able to automatically expand. Yes, they follow a fundamental principle: Only when the compiler has to generate a function for this class ( nontrival ), the compiler will actually generate it .

So, as the title says, let's not be fooled by the rules described in the C++ syntax. Indeed, I believe these rules will not bring much impact on our programming (don't generate errors), but only understand them behind the operation, we didn't know what the compiler has done for us, we will know how to use c + + to make it more efficient, such as eliminating unnecessary structure and virtual mechanism, etc. (if possible). I believe that the description of C++ automatically generated content in this article has made many people understand the causes and consequences of object constructors, I hope this article is helpful to you.


Related articles: