To a great extent, providing correct definitions for your classes (including class templates) and functions (including function templates) is a key part of the battle. Once you get the correct result, the corresponding implementation is very straightforward. But there are still some precautions to be careful. Defining variables too early will drag performance down. Excessive use of forced conversions can lead to slow, difficult to maintain, and code that is plagued by subtle bugs. Returning the handle of an internal component of a class will destroy the encapsulation and leave the blank handle to the customer. Neglected consideration of the impact on exceptions will lead to resource leakage and data structure destruction. Excessive inlining will lead to code expansion. Excessive coupling can lead to unacceptable long build Times. All these problems can be avoided.
Variable definition is postponed as long as possible.
As long as you define a variable with the type of constructor and destructor, when the control flow reaches the variable definition, you will be subject to the construction cost, when the variable leaves the scope, you will be responsible for analyzing the composition. If variables are used to create this cost, you have to do your best to avoid it.
You may think that you never define useless variables, but maybe you should think about it again. Consider the following function. As long as the length of the password meets the requirements, it returns an encrypted version of the password. If the password is too short, the function throws an exception of the logic_error type defined in the Standard C ++ Library (see Item 54 ):
// this function defines the variable "encrypted" too soon
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... // do whatever is necessary to place an
// encrypted version of password in encrypted
return encrypted;
}
The object encrypted is not completely useless in this function, but it is useless if an exception is thrown. In other words, even if encryptPassword throws an exception, you have to pay for the Construction and Analysis of encrypted. Therefore, you 'd better postpone the definition of encrypted until you are sure you really need it:
// this function postpones encrypted’s definition until it’s truly necessary
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
... // do whatever is necessary to place an
// encrypted version of password in encrypted
return encrypted;
}
This code is still not as compact as it could have been, because there is no initialization parameter when defining encrypted. This means that the default constructor will be used in many cases. The first thing you should do for an object is to give it some values, this can often be done by assigning values. I have explained why a default-constructing object is less efficient than initializing it with the value you really need. That analysis also applies. For example, assume that the core part of encryptPassword is completed in this function:
void encrypt(std::string& s); // encrypts s in place
EncryptPassword can be implemented in this way, even if it is not the best method:
// this function postpones encrypted’s definition until
// it’s necessary, but it’s still needlessly inefficient
std::string encryptPassword(const std::string& password)
{
... // check length as above
std::string encrypted; // default-construct encrypted
encrypted = password; // assign to encrypted
encrypt(encrypted);
return encrypted;
}
One more way to achieve this is to use password to initialize encrypted, thus skipping meaningless and possibly expensive default structures:
// finally, the best way to define and initialize encrypted
std::string encryptPassword(const std::string& password)
{
... // check length
std::string encrypted(password); // define and initialize
// via copy constructor
encrypt(encrypted);
return encrypted;
}
This suggestion is the true meaning of "as long as possible" in the title of this Item. You should not only postpone the definition of a variable until the last moment before you have to use it, but also try to postpone its definition until you get its initialization parameters. By doing so, you can avoid useless Object Construction and Analysis, and avoid unnecessary default structures. Furthermore, initializing them in context where their meanings are already very clear helps document the role of variables.
"But what will happen to the loop ?" You may have such questions. If a variable is only used in a loop, is it better to define it outside the loop and assign a value to it during each loop iteration, Or is it better to define this variable inside the loop? That is to say, which of the following two structures is better?
// Approach A: define outside loop // Approach B: define inside loop
Widget w;
for (int i = 0; i < n; ++i){ for (int i = 0; i < n; ++i) {
w = some value dependent on i; Widget w(some value dependent on i);
... ...
} }
Here, I replace a string object with a Widget object of the type to avoid any foreseeable cost of constructing, destructing, or assigning values to this object.
For Widget operations, the cost of the two methods is as follows:
Method A: 1 constructor + 1 destructor + n assignments.
Method B: n constructors + n destructor.
Method A is generally more efficient for classes whose assignment cost is lower than the cost of A constructor/destructor pair. Especially when n is very large. Otherwise, Method B may be better. In addition, compared with method B, method A makes the name w visible in A large area (including the area of the loop), which may damage the comprehensibility and maintainability of the program. Therefore, draw the following conclusion: unless you are sure that the following two points: (1) assignment is lower cost than constructor/destructor, and (2) you are involved in the Performance-sensitive part of your code. Otherwise, you should use method B by default.
Things to Remember
· Deferred variable definition as long as possible. In this way, the definition of the program can be increased and the program performance can be improved.