Reading Notes Objective c ++ Item 26: postpone variable definition as much as possible.
1. defining variables will cause constructor and destructor overhead
Whenever you define a type of variable: when the control flow reaches the definition point of the variable, you introduce the overhead of calling the constructor. After leaving the scope of the variable, you introduced the overhead for calling the destructor. Unused variables also produce overhead. Therefore, we should avoid this definition as much as possible.
2. The definition of variables in common functions is postponed. 2.1 variables may not be used.
You may think that you will never define unused variables. You may have to consider them again. Take a look at the following function. This function returns the encrypted version of the password. The provided password must be long enough. If the password is too short, the function will throw a logic_error type exception, which is defined in the Standard C ++ Library (Item 54 ):
1 // this function defines the variable "encrypted" too soon 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 using namespace std; 8 9 string encrypted;10 11 if (password.length() < MinimumPasswordLength) {12 13 throw logic_error("Password is too short");14 15 }16 17 ... // do whatever is necessary to place an18 19 // encrypted version of password in encrypted20 21 return encrypted;22 23 }
The object encrypted is not used at all, but will not be used if an exception is thrown. This means that if encryptPassword throws an exception, you will not use encrypted, but you will also pay for the encrypted constructor and destructor. Therefore, it is best to postpone the encrypted definition until you think you will use it:
1 // this function postpones encrypted’s definition until it’s truly necessary 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 using namespace std; 8 9 if (password.length() < MinimumPasswordLength) {10 11 throw logic_error("Password is too short");12 13 }14 15 string encrypted;16 17 ... // do whatever is necessary to place an18 19 // encrypted version of password in encrypted20 21 return encrypted;22 23 }
2.2 deferred variable definition method
The above Code seems to be not compact, because the encrypted definition does not contain any initialization parameters. This means that the default constructor will be called. In many cases, the first thing you do to an object is to provide it with some values, which are usually done by assigning values. Item 4 explains whyBy default, constructing an object is less efficient than assigning a value to it.. The analysis here also applies. For example, assume that the most difficult part of the encryptPassword function is executed in the following function:
1 void encrypt(std::string& s); // encrypts s in place
Then encryptPassword can be implemented as follows, although this may not be the best method:
1 // this function postpones encrypted’s definition until 2 3 // it’s necessary, but it’s still needlessly inefficient 4 5 std::string encryptPassword(const std::string& password) 6 7 { 8 9 ... // import std and check length as above10 11 string encrypted; // default-construct encrypted12 13 encrypted = password; // assign to encrypted14 15 encrypt(encrypted);16 17 return encrypted;18 19 }
2.2 A better way to postpone variable definition
A better way is to use the password to initialize enpolicted, which skips meaningless and potentially expensive Default constructors:
1 // finally, the best way to define and initialize encrypted 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 ... // import std and check length 8 9 string encrypted(password); // define and initialize via copy10 11 // constructor12 13 encrypt(encrypted);14 15 return encrypted;16 17 }
2.3 postpone the true meaning of variable definitions
This suggestion is the true meaning of "try to postpone" in the title of this clause.You should not only postpone the definition of a variable until you have to use it, but also try to postpone the definition until you get the initialization value of the variable.. In this way, you can avoid unnecessary constructor and destructor, and avoid unnecessary default constructor. And,By initializing a variable in a specific context, you can also specify the intent to use the variable..
3. How to Deal with variable definitions in a loop
Now you should think: what should we do with loops? If a variable is used only in one loop, is it to define the variable outside the loop, and assign a value to it for each loop iteration? Or is it better to define it inside the loop? Which of the following structures is better?
1 // Approach A: define outside loop 2 3 Widget w; 4 5 for (int i = 0; i < n; ++i) { 6 7 w = some value dependent on i; 8 9 ... 10 11 } 12 13 14 15 // Approach B: define inside loop16 17 for (int i = 0; i < n; ++i) {18 19 Widget w(some value dependent oni);20 21 ...22 23 }
Here, I use a Widget type object to replace the string type object to avoid any prejudice against the overhead of the constructor, destructor, or value assignment operator.
For widgets, the overhead of the two methods is as follows:
- Method 1: 1 constructor + 1 destructor + n assignment operations
- Method 2: n constructors and n destructor
If the overhead of the value assignment operation is smaller than that of A pair of constructor/destructor, method A is more efficient. Especially when n is large. Otherwise, Method B should be more efficient. In addition, method A is more visible than method B in variable w, which violates the comprehensibility and operability of the program. Therefore, unless you encounter the following two points: (1) assignment is less costly than constructor/destructor (2) You are processing performance-sensitive code. Otherwise, you should use method B by default.