-
- Clause 26 to postpone the occurrence of variable definitions as long as possible
- Clause 27 try to do less transformational action
Clause 26: Delay the occurrence of variable definitions whenever possible
Define a variable in the program, when the control flow reaches the variable definition, the program will bear the cost of the variable construction, and when the control flow leaves the scope, the program also has to withstand the composition of the analysis. Regardless of whether or not this variable is used, these costs are to be borne. This situation should be avoided as much as possible. Maybe you don't think you're going to use it like that, but it's not. For example, to write an encryption function, but encrypt the password to be long enough. If the password is too short, an exception is thrown Logic_error (c + + standard library, * * clause **54).
std::string encryptPassword(const std::string& psaaword){ using namespace std; string encrypted; if(password.length()<MinimumPasswordLength) { throw logic_error("Password is too short"); } ……//加密密码,把加密结果放到encrypted内 return encrypted;}
If this function throws an exception, then the variable encrypted will execute the constructor and destructor even if it is not used.
So the above function should postpone the definition of encrypted until it is really needed
std::string Encryptpassword (const std::string& Psaaword)
{
using namespace Std;
if(password.length()<MinimumPasswordLength) { throw logic_error("Password is too short"); } string encrypted; ……//加密密码,把加密结果放到encrypted内 return encrypted; }
But this piece of code is not efficient enough. When using a variable encrypted, define it before assigning it a value. As mentioned in the * * clause **4, it is not efficient to use a constructor that directly specifies the initial value to construct an object by using the default constructor and then assign the value with the assignment operator.
std::string encryptPassword(const std::string& psaaword) { using namespace std; if(password.length()<MinimumPasswordLength) { throw logic_error("Password is too short"); } string encrypted;//定义 encrypted=password;//赋值 encrypt(encrpted); ……//加密密码,把加密结果放到encrypted内 return encrypted; }
It is less efficient than
std::string encryptPassword(const std::string& psaaword) { using namespace std; if(password.length()<MinimumPasswordLength) { throw logic_error("Password is too short"); } string encrypted(password);//定义+赋值 encrypt(encrpted); ……//加密密码,把加密结果放到encrypted内 return encrypted; }
The term "defer as much as possible" is not just to delay the definition of a variable until the moment the variable is used, or even try to postpone the definition until it is able to assign an initial value. This also avoids the meaningless default constructor.
So what do you do when you cycle? Define a variable outside the loop or within a loop?
Code A
Widget w;//定义在循环外for(int i=0;i < n;++i) w=……; ……}
Code b
for(int i=0;i<n;++i){ Widget w(……);//定义并赋值 ……}
Price
Code A; 1 constructors + 1 destructors +n assignment operation
Code B:n A constructor +n a destructor
If the class assignment cost is lower than the construction cost + analysis composition, code A is efficient. Otherwise, code B is efficient. However, in code a, the scope of W is larger than code B, which may conflict with the understanding and maintainability of the program, so unless (1) You know that the cost of the assignment is lower than the construction + destruction cost, (2) You are dealing with the code in a highly efficient and sensitive way. Otherwise you should use code b
summarize The occurrence of variable definitions as long as possible. This can increase the clarity of the program and improve efficiency.
Clause 27 try to do less transformational action
One of the design goals of C + + rules is to ensure that "type errors" do not occur. If you compile with a type-conversion-related warning, don't abandon it easily, and make sure the warning is safe.
Transformation in C + + (casts) destroys the type system. C + + differs from C, Java, and C #, and the transformations in these languages are more necessary, difficult to avoid, and less dangerous.
The next three kinds of transformation grammar
C-style transformation
(T)expression
Transformation of Function style
T(expression)
There are no differences between the two forms, except that the parentheses are positioned differently.
Four new types of transformation in C + +
const_cast<T>(expression)dynamic_cast<T>(expression)reinterpret_cast<T>(expression)static_cast<T>(expression)
Each of these four different uses
**1.**const_cast is often used to remove the constant characteristics of an object (cast away the constness). It is also the only C++-style transition operator with this capability.
**2.**dynamic_cast is primarily used to perform "safe downcasting", which is used to determine whether an object belongs to a type in the inheritance system. It is the only action that cannot be performed by old-fashioned syntax, and it is the only transformational action that can cost significant operational costs (in the following detail).
**3.**reinterpret_cast intent to perform a low-level transformation, the actual action (result) may depend on the compiler, which indicates that it is not portable. For example, to convert pointer to int into int, such transformations are often used in low-level code. For example, the discussion discusses how to write a debug allocator for raw memory (debugging allocator), see clause 50.
**4.**static_cast performs a forced implicit conversion (implicit conversions). For example, convert int to Double,non-const to constant. It can also be used to perform some conversion of the echo conversion, but cannot convert the const to Non-const.
In order to be compatible, the old-fashioned transformation is still legal, but more advocated in the new form. Because 1, the new transformation is easy to identify, you can quickly find out what transformation in the code. 2, the new transformation of the goal of the more narrow, the compiler is more likely to diagnose the use of errors.
But when you pass an object to a function with the explicit constructor, you often use legacy transformations:
class Widget{public: explicit Widget(int size); ……};void doSomeWord(const Widget& w);doSomeWork(Widget(15));//函数风格doSomeWork(static_cast<Widget>(15));//C++风格
When using C + + style transformation, it's not much like generating objects, but the function style looks more natural. But it's just a feeling, it just looks like it could be the time for core dump.
Transformation on the surface seems to treat one type as another type, but the compiler does much more than that. The compiler will actually compile code during the runtime during the compiler. For example, the following code:
int x,y;……double d=static_cast<double>(x)/y;
Convert x to double, and without transformation, the code must be different. Because the underlying representations of double and int are not the same. The following example is more obvious
class Base{……};class Derived:public Base{……};Derived d;Base* b=&d;//隐喻的将Derived*转为Base*
The above code is one of the means of implementing polymorphism, often seen. The size of the base object and the derived object are not the same. At this point, an offset (offset) is usually applied to the derived* pointer during operation to obtain the correct base* pointer.
The above also describes a single object, which may have multiple addresses, such as a Derived object, a derived* pointer to it, and a base* pointer to it. Such a thing often happens in succession. Often you should not assume that objects are laid out in memory, and should not be transformed on the basis of this assumption. For example, transform base* to char* for character manipulation.
But sometimes we need an offset to know the layout of the object and transform it. But different compilers can cause different objects to be laid out, which means that code that is compiled on one platform may not be able to be done on another platform.
Sometimes it's easy to write specious code. For example, in many application frameworks (application frameworks) requires that the first line of the virtual function code in derived classes be called the virtual function corresponding to the base class. For example, there is a window base class and a Specialwindow derived class. Both define the OnResize function, and the onresize function in Specialwindow calls the OnResize function in window first. Here's a way to do it, it looks right, but it's wrong.
class Window{public: virtual void onResize(){……}; ……};class specialWindow:public Window{public: virtual void onResize(){ static_cast<Window>(*this).onResize();//将this转为Window,然后调用。这样其实不行。 …… }};
In this code, the transformation action is used, and what we expect is to turn this into window and then call Window::onresize. In fact, this is not the case, the static_cast<Window>(*this).onResize()
call is the "base class component" in this onresize () (only one copy of the function, call the function of the object does not have any relationship, the key is that the function is hidden the this pointer, will affect the function operation of the data). To achieve the desired behavior, it is very simple to use the domain operator::
class specialWindow:public Window{ public: virtual void onResize(){ Window::onResize(); …… } };
The
Example also shows that if you're going to use transformations, chances are you're moving in the wrong direction. This is especially true with dynamic_cast.
before delving into dynamic_cast, it is important to note that many implementations of dynamic_cast run very slowly. There is a very common implementation version based on the "class name of string comparison". If you execute dynamic_cast on an object in a single inheritance system that inherits from layer four, the implementation version that you just mentioned may consume up to four strcmp calls per dynamic_cast call to compare the class name. If it is deep inheritance or multiple inheritance, the cost is higher. Of course, some versions do this for some reason (it must be a dynamic link only). So be skeptical about the general transformation, especially the use of dynamic_cast in efficiency-focused code. The
uses dynamic_cast because we have only one base pointer or reference on hand, but we want to perform a derived class operation on the derived object. There are usually two general operations to avoid this problem.
1, uses the container where a pointer to derived (usually a smart pointer, clause 13) is stored. This eliminates the need for the derived object to be processed through the base class interface. However, this implementation cannot store the pointer "point to all possible derived classes" in the same container. More than one container is typically required, and each container stores one type, which must have type safety (type-safe).
2, can handle "all possible various window derived classes" through the base class interface, that is, the base virtual function does what you want to do with the derived class. For example, you can declare an empty virtual function at the base class, with different implementations in different derived classes, to achieve what you want to do with polymorphism.
Either way, it is not universally accurate, and in many cases, a viable alternative to dynamic_cast is available. But one thing to avoid is the so-called "string (cascading) dynamic_cast", which is what looks like this:
class window{...} ;.....//derived classed defined here typedef std::<vector<std::tr1::shared_ptr<window> > VPW; VPW winptrs;......for (Vpw::iterator iter=winptrs.begin (); Iter!=winptrs.end (); ++iter) {if (specialwindow1* psw1=dynamic_ Cast<specialwindow1*> (Iter->get ())) {... ) Else if (specialwindow2* psw1=dynamic_cast<specialwindow2*> (Iter->get ())) {... ) Else if (specialwindow3* psw1=dynamic_cast<specialwindow3*> (Iter->get ())) {... ) ...}
Such code is big and slow, for example, if the window inheritance system changes, then you have to check to see if you need to modify it. If you join the new derived class, then add new code and join the new Judgment branch. Such code should be replaced by virtual functions.
Good C + + code should seldom be used for transformation, but it is impractical to be completely free of transformation. In the beginning it will int
be double
a reasonable use, although we can initially declare it as a double type to avoid transformation. As with many constructors, it is common to isolate transformations, hide transformations within functions, and interface to ensure that callers are unaffected by the transformation.
Summary
1, should use less transformation, especially in the efficiency-oriented code to use dynamic_cast. If you need to transform, try to design without transformation instead.
2, if must use transformation, hides it in the function, the customer uses through the interface, but is not the customer to realize the transformation.
3, using the new C + + style transformation to replace the old transformation, because the new transformation is easy to identify, and they are classified.
"Effective C + +" resource management: Clause 26-clause 27