Optimal features of c ++)

Source: Internet
Author: User

If you want to learn all the content of C ++, this is a huge, complex, and full of traps. If you see some people using it, you may be frightened. Currently, new functions are gradually added to the C ++ standard. Therefore, it is unrealistic to learn the details of the language without accumulating for many years.

However, you do not need to learn all aspects of the language before you can write programs. To use C ++ efficiently, you only need to learn several basic features of it. In this article, I am going to introduce one of the most important features in C ++. This feature is also the reason why I chose C ++ instead of other programming languages.

Determined object life-time)
Each object you create in a program has a precise and definite lifecycle. Once you are sure to use a certain life cycle, you will know exactly when and when the life cycle of the object you create begins.

For local variables, their life cycle starts from their declaration (after the normal initialization process ends, there is no exception) until they exit the scope. For two adjacent local variables, the lifecycle of the preceding variables starts earlier and ends later.

For function parameters, their lifecycles start before the function starts and end after the function is executed.

For global variables, their lifecycles start before the main function ), it ends after the main function is executed. It is defined as two global variables in the same compilation unit (: translation unit, C ++, the definition starts earlier and ends later. For two global variables defined in different compilation units, we cannot make assumptions about the relationships between them in their lifecycles ).

For almost all temporary variables (except the two well-defined exceptions ), their life cycle starts from a function in a long expression that is returned by passing a value (or explicitly creating it) until the entire expression is evaluated.

The following are two exceptions: When a temporary object is bound to a global or local reference, its lifecycle is as long as that of the referenced object.


Base & B = Derived {};
Int main ()
{
//...
B. modify ();
//...
}
// The lifecycle of a temporary object of the Derived type ends here (note that the referenced type and temporary object type are not the same, and we can modify this temporary object. "Temporary" indicates that the existence time is "temporary", but when it is bound to a global reference, its lifecycle is as long as that of any other global variables .)

The second exception applies to initializing an array of user-defined types. In this case, if a default constructor is used to initialize the nth element of the array, and the default constructor has one or more default parameters, the lifecycle of each temporary object created in the default parameter ends when we continue to initialize the n + 1 element. However, you probably do not need to know this when writing a program.

For member objects within the class, their life cycle starts before the object's life cycle begins and ends after the end of the object's life cycle.

The lifecycles of other types of objects are similar: local static variables of functions, thread-local variables, and variable lifecycles that we can manually control, such as new/delete and optional. In these cases, the beginning and end of the object lifecycle are well defined and predictable.

During object initialization, its lifecycle is about to begin. However, if an exception occurs at this time, its lifecycle does not actually begin.

In short, this is the essence of the object lifecycle. So what is an uncertain object lifecycle? C ++ does not yet exist (for the moment), but you can see it in other languages with support for "Garbage Collection. In this case, when you create an object, its lifecycle begins, but you do not know when its lifecycle ends. Garbage collection guarantees that if you reference an object, the lifecycle of the object will not end. However, if the last reference pointing to this object does not exist, its survival time may be any long until the end of the entire process.

So why is the life cycle of a specified object so important?

Destructor
C ++ ensures that all types of objects call their destructor at the end of their lifecycle. The Destructor is a member function of the class where the object is located and is ensured to be the final called function of the Class Object.

All know this, but not all know the benefits it brings to us. First, the most important thing is that you can use destructor to clear the resources that your object obtains in its lifecycle. This cleanup is encapsulated and invisible to users: users do not need to manually call any dispose or close functions. Therefore, you generally do not forget to clean up resources. You do not even need to know whether the classes you are currently using have managed resources. In addition, when an object is destroyed, its resources will be cleared immediately, rather than in an uncertain future. The sooner resources are released, the better. This prevents resource leakage. In this process, no garbage is left, and no resources need to be cleared at a specified time. (When you see the word "resource", don't just think about memory, think about opening a database or socket connection .)

The object lifecycle also ensures the relative sequence of object destruction. If a scope contains several local objects, they will be destroyed in the reverse order of Declaration (and initialization. Similarly, for internal objects of a class, they are destroyed in the reverse order declared (and initialized) in the class definition. In essence, this ensures the correctness of the dependency between resources.

This feature is garbage collection for the following reasons:

1. It provides a unified approach for all the resource management you can think of, not just the memory;

2. Resources will be released immediately when they are no longer in use, rather than being recycled to decide when to clean up;

3. It will not bring additional overhead for running time like garbage collection.

The garbage collector-based language tends to provide an alternative to resource management: The using statement in C # Or the try statement in Java. Although they are in the good direction, they are not as good as destructor usage.

1. Resource Management is directly exposed to users: You need to know the type you are currently using to manage the memory, and then add additional code to request the release of resources;

2. If the class maintainer decides to change a class that originally does not manage resources to a class that manages resources, the user needs to modify his own code, which is a problem caused by unencapsulated resource cleaning;

3. This method cannot be used with generic programming: You cannot write code that uses the unified syntax for classes that process and do not process resources.

Finally, this protection block (guarding statements) it can only replace the processing method of "local" objects (that is, objects created in a function or a statement block) in C ++. C ++ also provides other types of object lifecycle. For example, you can make a resource management object a member of another "master" object, expressed in this way: the lifecycle of this resource continues until the lifecycle of the primary object ends.

Consider opening n file streams and placing them in a function returned by a container. There is also a function that reads these file streams from the container and closes them automatically:


Vector <ifstream> produce ()
{
Vector <ifstream> ans;
For (int I = 0; I <10; ++ I ){
Ans. emplace_back (name (I ));
}
Return ans;
}
 
Void consumer ()
{
Vector <ifstream> files = produce ();
For (ifstream & f: files ){
Read (f );
}
} // Close all files. If you want to use the using or try statements, how do you implement this function?

Note that there is a tip here. Another important feature in C ++ is used: the move constructor. It also uses the basic fact that std: fstream cannot be copied, but can be moved. (However, GCC 4.8.1 users may not pay attention to this) The Same Thing (pass-through) occurs on std: vector <std: ifstream>. The move operation simulates the lifecycle of another unique object. In this process, we have "virtual" and "Manual" lifecycles of resources (collection of file handles), in which the life cycle starts from the creation of ans, it ends at the end of the lifecycle of different objects defined in another scope.

Note that the entire collection of file handles throughout the "extended" lifecycle, if an exception occurs, each handle will be protected and will not be leaked. Even if the name function has encountered an exception in 5th iterations, the four elements that have been created before will be correctly analyzed in the produce function.

Similarly, you cannot use the "Protect" statement to achieve the following results:


Class CombinedResource
{
Std: fstream f;
Socket s;
 
CombinedResource (string name, unsigned port)
: F {name}, s {port }{}
// The Destructor is not explicitly called.
}; This code has provided you with several useful security measures. The two resources will be released at the end of the CombinedResource lifecycle: this is handled in implicit destructor in the reverse order of initialization, and you do not need to manually write the code. Assume that an exception occurs in the constructor when initializing the second resource s. The initialized f destructor will be called immediately, this process has been completed when an exception is thrown from the constructor of s. You can get the security protection for free.

How do you use using or try to ensure the above security?

Poor
It is necessary to mention some reasons why some people do not like destructor. In some cases, garbage collection is better than the resource management method provided by C ++. For example, with the garbage collector (if you can use it), you can just allocate nodes and connect them through pointers (you can also call them "references, to represent a graph with a ring. In C ++, you cannot do this, or even use smart pointers. Of course, the nodes in the graph managed by garbage collection cannot manage resources because they may leak: The using or try statements do not work here, because the finalizer function may not be called.

Also, I have heard some people say that some efficient parallel algorithms can only be completed with the help of the garbage collector. I admit I have never seen such an algorithm.

Some people do not like to see the destructor in the Code. Some people like this method, and others do not like it. When analyzing and debugging a program, you may not notice that a destructor has been called, and this may have some side effects. I once fell into this trap when debugging a big and messy program. An object pointed by a raw pointer may suddenly become invalid for an unknown reason, and I cannot see any function that causes this situation. Later, I realized that the same object was pointed to by another unique_ptr, and this unique_ptr quietly exceeded the scope. For a temporary object, the situation may be worse. You neither see the Destructor nor the object itself.

There are some restrictions when using destructor: in order to expand the Destructor and stack (stack unwinding, caused by exceptions, is a standard process for exception handling in C ++) correct collaboration. They cannot throw exceptions themselves. This restriction is very difficult for some people, because they need to mark the failure of resource release, or use the Destructor for other purposes.

Note: In C ++, unless you define the Destructor as notest (false), it is implicitly declared as notest. if an exception is thrown from it, std: terminate will be called. If you want to mark a resource release failure when an exception occurs, we recommend that you provide a member function like release to display the call, and then let the Destructor check whether the resource has been released, if not, release it quietly (swallow any exception without continuing to throw it ).

Another potential drawback of releasing resources through destructor is that sometimes you need to introduce additional manual scopes (or blocks) in your function ), this is only to trigger the destructor of a local object before the function scope ends. For example:


Void Type: fun ()
{
DoSomeProcessing1 ();
{
Std: lock_guard <std: mutex> g {mutex _};
Read (sharedData _);
}
DoSomeProcessing2 ();
} Here, we have to add an additional program block to ensure that mutex is not locked when we call the doSomeProcessing2 function: We want to release the resources immediately after they are stopped. This looks a bit like a using or try statement, but there are two differences:

1. This is an exception, not a rule;

2. If we forget this scope, the resource will be held for a longer period of time, but it will not be leaked because its destructor is bound to the caller.

This is what I want to talk about. I personally feel that destructor are the best and practical feature in all programming languages, and I have not mentioned other advantages: interaction with exception handling mechanisms. This is a feature that attracts me more in C ++ than in performance: elegance.

 

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.