Reading notes effective C + + ITEM4 Ensure that objects are initialized before they are used

Source: Internet
Author: User

ITEM4 to ensure that objects are initialized before they are used

C + + is mutable on object initialization, such as the following example:

Int x;

In some contexts, X is guaranteed to be initialized to 0, and in some other cases it cannot be guaranteed. Look at the following example:

Class Point

{

Int x, y;

};

Point P;

The data members of P are sometimes guaranteed to be initialized (to 0) and sometimes not. If you are transiting to C + + from a language that does not have an uninitialized object, you need to be aware of it because it is important.

Disadvantages of using an uninitialized object

Reading an uninitialized value results in undefined behavior. In some platforms, simply reading the uninitialized value will stop your program. It is more likely to read in some semi-random bits, which will contaminate your objects and eventually lead to incredible program behavior and a lot of unpleasant program debugging.

Description of the initialization of the built-in type and non-built-in type

Now, there are rules that describe when an object initialization is guaranteed to occur and when it cannot be guaranteed. Unfortunately, these rules are too complicated-it's not worth remembering. In general, if you use the C part of C + +, initialization can incur run-time costs, so there is no guarantee of initialization. If you enter the non-C part of C + +, things will change. This explains why an array (the C-language part of C + +) does not guarantee that its contents are initialized, but the vector is guaranteed (from the STL section of C + +).

How to ensure that the built-in type is initialized

The best way to handle this seemingly indeterminate state transaction is to always initialize the objects before you use them. For a non-member object of the built-in type, you need to initialize it manually. As an example:

int x = 0; Manual initialization of an int

const char * Text = "A c-style string"; Manual initialization of a pointer (see also Item 3)

Double D; "Initialization" by reading

Std::cin >> D; An input stream

How to ensure that non-built-in types are initialized

For something other than the built-in type, the responsibility for the initialization falls on the constructor. The rule is very simple: make sure that all constructors initialize all things of the object.

This rule is easy to follow, but it is important not to confuse assignment and initialization. Consider a class that represents the address book, with its constructor as follows:

Class PhoneNumber {...};

Class Abentry {//abentry = "Address book Entry"

Public

Abentry (const std::string& Name, const std::string& address,

const std::list<phonenumber>& phones);

Private

Std::string thename;

Std::string theaddress;

Std::list<phonenumber> Thephones;

int numtimesconsulted;

};

Abentry::abentry (const std::string& Name, const std::string& address,

Const std::list<phonenumber>& Phones)

{

thename = name; These is all assignments,

theaddress = address; Not initializations

Thephones = phones;

numtimesconsulted = 0;

}

Using initialization lists in constructors is more efficient than assigning values

This will produce the Abentry object you need, but this is still not the best approach. The rules of C + + stipulate that a data member of an object is initialized before it enters the constructor body. Within the Abentry constructor, thename,theadress and Thephones are not initialized, they are assigned values. Initialization occurs earlier, and the default constructors for these data members are called automatically before entering the Abentry constructor body. However, this does not apply to numtimesconsulted because it is a built-in type. For built-in types, it is not guaranteed to be initialized prior to assignment.

A better way to write the Abentry constructor is to use the member initialization list instead of assigning values.

Abentry::abentry (const std::string& Name, const std::string& address,

Const std::list<phonenumber>& Phones)

: thename (name),

Theaddress (address),//These is now all initializations

Thephones (phones),

numtimesconsulted (0)

{}//The ctor body is now empty

This constructor will produce the same result as the above constructor function. But it will be more efficient. An assignment-based version first invokes the default constructor to initialize Thename,theaddress and Thephones, and then quickly assigns a value on top of the member that is constructed by default. All the work done in the default constructor is therefore wasted. The use of member initialization lists avoids this problem because the parameters in the member initialization list are used as arguments to the constructors of different data members. In this case, Thename will be constructed with name as the parameter copy, Theaddress will be constructed with address as the parameter copy, Theaddress will be constructed with phones as the parameter copy. For most types, invoking a single copy constructor is more efficient than calling the default constructor at first and then invoking the copy assignment operator, and sometimes the efficiency can be greatly improved.

For built-in types like numtimeconsulted, the overhead of initialization and assignment is the same, but for consistency, it is best to initialize everything through an initialization list. Similarly, when you want to construct a data member using the default constructor, you can still use the member initialization list: Initialize parameters don't specify anything. For example, if Abentry has a constructor with no parameters, it can be implemented as follows:

Abentry::abentry ()

: Thename (),//Call thename ' s default ctor;

Theaddress (),//Do the same for theaddress;

Thephones (),//And for thephones;

numtimesconsulted (0)//But explicitly initialize

{}//numtimesconsulted to zero

Using initialization lists in constructors is more difficult to make mistakes

For a data member of a user-defined type that is not listed in the member initialization list, the compiler automatically calls the default constructor for it, which is too much to think about. This is understandable, but if there is a rule: All data members are always listed in the member initialization list. This way we don't have to remember specifically which data members are not initialized without being ignored. Because Numtimesconsulted is an built-in type, if it is not placed in the member initialization list, it will not be initialized and open the door to undefined behavior.

Sometimes member initialization lists must be used, even for built-in types. For example, a const data member or reference data member must be initialized without being assigned a value (ITEM5). To avoid the need to remember when a data member must be initialized when it is optional, the simplest option is to always use the initialization list. Sometimes it is necessary to do so, it is more efficient than the assignment.

The exception, when to use the assignment will be better

Many classes have multiple constructors, and each constructor has its own initialization list. If these classes have many data members and/or base classes, the existence of multiple initialization lists introduces unpleasant repetitions (duplicates of different constructor initialization lists), and programmers get bored. In this case, it is reasonable to omit those members whose assignments are equally efficient in the member initialization list and move those assignments to a separate function (private) for all constructor calls. This is especially helpful if the initialization value of a data member is derived from a file or a lookup database. In summary, a true data member initialization (through a member initialization list) is better than "fake" initialization by assigning a value.

Description of Class object member initialization order

A constant place in C + + is the order in which data members of an object are initialized. This order will always be the same: the base class is initialized before the derived class is initialized (ITEM12), and within the class, the data members are initialized according to the order in which they are declared in the class. For example, in Abentry, Thename is initialized first, thesecond second, thephones the third is initialized, numtimesconsulted is finally initialized, Even if the data members are listed in a different order in the initialization list (unfortunately this is legal), the order of initialization is also in the order of Declaration. In order to prevent confusion for people reading code, and in order to prevent some ambiguous errors, it is best to have the data member order listed in the member initialization list consistent with the order of Declaration.

Description of Non-local static object initialization order
    • Problem description

Once you are careful to initialize the members of the built-in type, and you are able to ensure that the base class and data members are initialized with the initialization list in the constructor, there is only one thing you need to worry about. This is the initialization order of non-local static objects defined in different compilation units.

Our 1.1-point analysis of this sentence.

The lifetime of a static object is started from the time the object is built until the end of the program. The object lifetime based on stacks and heaps is not listed. The object types in this column include global objects, namespace-scoped object definitions, static objects inside the class, static objects declared inside the function, and static objects declared within the file scope. A static object inside a function is called a local static object (as opposed to a function that is local) and other types of static objects are non-local (non-local) objects. Static objects are destroyed when the program exits, for example: The destructor is called when the main function finishes executing.

A compilation unit is the source code that can produce a single obj file. Basically, it's a single source file, plus all the files that include it.

The problem we are concerned with is at least two separately compiled source files, each containing at least one non-local (non-local) static object (for example, a global object in the namespace scope or a static object within a class or file scope). The real problem is that if the initialization of a non-local (non-local) static object in a compilation unit uses a non-local static object in a different compilation unit, the object it uses might not be initialized because the relative order of initialization of non-local static objects defined in different compilation units is undefined.

See the example below. Suppose you have a filesystem class that makes files on the internet look like local. Since your class makes the world look like a single file system, you will create a global or namespace-scoped special object to represent this single filesystem.

Class FileSystem {//from your library ' s header file

Public

...

std::size_t numdisks () const; One of many member functions

...

};

extern FileSystem TFS; Declare object for clients to use ("TFS" = "The file system");                                                                                                                             Definition is in some. cpp file in your library

A filesystem is an important object, so using TFS before initializing a TFS object can be disastrous.

Now assume that the client has created a class for the folder in a file system. Naturally, this class will use the TFS object:

Class Directory {//Created by library client

Public

Directory (params);

...

};

Directory::D irectory (params)

{

...

std::size_t disks = Tfs.numdisks (); Use the TFS Object

...

}

Further assume that the client decides to create a single Folder object for the temporary file:

Directory tempdir (params); Directory for temporary files

The importance of the initialization order is now obvious: unless you initialize TFS prior to tempdir initialization, Otherwise, the TempDir constructor attempts to use the uninitialized TFS. Because TFS and TempDir are created by different people at different times in different source files, they are non-local static objects that are defined in different compilation units. How can you guarantee that TFS will be initialized before TempDir?

You cannot guarantee that the relative order of initialization of non-local static objects defined in different compilation units is undefined. This is for a reason, and it is difficult to determine the "appropriate" initialization order for non-local static objects. In its most common form, there are non-local static objects that are generated by implicit templates in multiple compilation units, in which case not only cannot determine the correct order of initialization, but it is also not worthwhile to look for specific cases in the correct order that might determine the initialization.

    • How to solve the problem?

Fortunately, a small design change can eliminate the whole problem. All you have to do is move each non-local static object into a function and declare it as static in the function. These functions return a reference to the object they contain. The client can invoke the function instead of referencing the object directly. In other words, non-local static objects are replaced by local static objects. (lovers of design patterns will find that this is the general implementation of the Singleton pattern.) )

C + + guarantees that the first time a function call encounters a local static object defined within the function, the object is initialized. Therefore, if you replace direct access to a non-local static object by using a function called a local static object as the return value, you can guarantee that the returned object reference points to the object being initialized. There is also a benefit that if you never call this function to replace a non-native object, there will never be any overhead for constructors and destructors, which will not take effect for non-native objects.

The above technologies should be on TFS and TempDir:

Class FileSystem {...}; As before

filesystem& TFS ()//This replaces the TFS object; It could be static in the FileSystem class

{

Static FileSystem FS; Define and initialize a local static object

return FS; Return a reference to it

}

Class Directory {...}; As before

Directory::D irectory (params)//as before, except references to TFS is now to TFS ()

{

...

std::size_t disks = TFS (). Numdisks ();

...

}

directory& tempdir ()//This replaces the TempDir object; It could is static in the Directory class

{

Static Directory TD (Params); Define/initialize local Static object

return TD; return reference to it

}

The modified System program client is the same as before the modification, except that TFS () and TempDir () are now used instead of TFS and TempDir. That is, use the object itself instead of using the function that points to the object reference as the return value.

    • limitations of workaround and usage scenarios  

The reference-return function of this rule description is usually simple: define and initialize a local object in the first row, and return in the second row. This simplicity makes it an absolute candidate for inline functions, especially if they are frequently called (ITEM30). In addition, the fact that these functions contain static objects makes it problematic to use in multithreaded systems. Again, any type of Non-const static object, whether local or non-local, will have a problem waiting for something to happen in a multithreaded environment. One way to solve this problem is to manually trigger all references-return functions in the single-threaded startup phase of the program. This method eliminates the initiation of the associated racing Form (race conditions).

Of course, using the reference-return function method prevents the initialization order problem, assuming that the object that needs to be initialized must first have a reasonable initialization order. If object A must be initialized in a system before object B is initialized, and object A's initialization depends on object B, there is a problem. If you can avoid this morbid scenario, the method described here can serve you well, at least in a single-threaded application.  

Summarize

To avoid being used before object initialization, there are three things you need to do.

First, initializes the built-in object manually.

Second, initializes all parts of an object using the member initialization list.

thirdly, redesign the scene with indeterminate initialization order.

Reprint please indicate the source

Reading notes effective C + + ITEM4 Ensure that objects are initialized before they are used

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.