Reading Notes Objective c ++ Item 27 use transformations as little as possible

Source: Internet
Author: User

Reading Notes Objective c ++ Item 27 use transformations as little as possible
 

C ++ is designed to ensure that type-related errors no longer occur. Theoretically, if your program can be compiled cleanly, it will not try to perform any unsafe or meaningless operations on any object. This guarantee is very valuable and should not be abandoned easily.

Unfortunately, casts subverts the type system. It causes a variety of troubles, some of which are easy to identify, and some are tricky (not easy to recognize ). If you have used C, java, or C # Before, you need to pay attention to it, because in these languages, casting is more essential, but it is safer than C ++. C ++ is neither C nor java nor C #. In C ++, you need to use casting with great respect.

1. Review of New and Old cast styles 1.1 cast styles

Let's first review the casting syntax. There are usually three different methods to implement the same cast. The C-style casts is as follows:

1 (T) expression // cast expression to be of type T

 

Function-style casts uses the following syntax:

1 T(expression) // cast expression to be of type T

 

The above two forms have no difference in meaning, except that the brackets are different. The two forms of casts are called the old-style casts.

1.2 C ++-style cast

C ++ also provides four new casts forms (C ++-style casts ):

1 const_cast<T>(expression)2 dynamic_cast<T>(expression)3 reinterpret_cast<T>(expression)4 static_cast<T>(expression)

 

Each method has a unique purpose:

  • Const_cast is used to remove the constness of an object ). Among the four C ++ cast types, const_cast is the only one that can achieve this.
  • Dynamic_cast is mainly used to implement "secure downward transformation", that is, to determine whether a specific type of object is in an inheritance system.This is the only one that cannot be implemented using the old style syntax.Cast.It is also the only one that may have a huge runtime overhead.Cast. (I will explain it in detail later)
  • Reinterpret_cast is used for low-level casts. The result may depend on the compiler, that is, the Code cannot be transplanted. For example, convert a pointer to an int. This type of casts is rare in other places except for low-level code. This book appears only once, discussing how to implement a debugging distributor (Item 50) for the native memory (raw memory ).
  • Static_cast can be used to convert the display type forcibly (for example, converting a non-const object to a const object (Item 3), or converting an int to a double object .) It can also be used to reverse these conversions (for example, void * is converted to a specific type pointer, And the pointer to base is converted to a pointer to a derived class ), but it cannot be converted from const to non-const object (only const_cast can do this ).
1.3 old style PK New Style

The old-style casts is still valid, but the new style is better. First, they are more easily identified in the Code (for people or tools), thus simplifying the process of looking for transformation actions in the code. Second, the more special use of each cast makes it possible for the compiler to diagnose usage errors. For example, if you use the other three cast instead of const_cast to remove constants, your code cannot be compiled.

The only thing I use legacy cast is when I want to call an explici constructor to pass an object to a function. For example:

 1 class Widget { 2 public: 3 explicit Widget(int size); 4 ... 5 }; 6 void doSomeWork(const Widget& w); 7 doSomeWork(Widget(15));                     // create Widget from int 8 // with function-style cast 9 10 doSomeWork(static_cast<Widget>(15));      // create Widget from int11 // with C++-style cast

 

In a sense, the creation of such an object is not like a cast, so function-style cast instead of static_cast is used. (These two methods do the same thing: create a temporary Widget object and pass it to doSomeWork .) I need to repeat it again. The Code implemented using the old-style transformation often feels reasonable at the time, but core dump may appear in the future, so it is best to ignore this feeling and always use the new-style casts.

2. Using cast will generate runtime code-do not think that what you think is what you think

Many Programmers think that cast does not do anything except to tell the compiler to treat a type as another type, but this is a misunderstanding. Conversion of any type (whether cast or implicit conversion is displayed) generates runtime code. For example:

1 int x, y;2 ...3 double d = static_cast<double>(x)/y; // divide x by y, but use4 // floating point division

 

Converting int x to double will certainly produce code, because in most system architectures, the underlying expression of int is different from that of double. This may not surprise you, but the following example may highlight your eyes:

1 class Base { ... };2 class Derived: public Base { ... };3 Derived d;4 5 Base *pb = &d;                                    // implicitly convert Derived* ⇒ Base*

 

Here we just created a base class pointer pointing to the object of the derived class,SometimesThe values of the two pointers (Derived * and Base *) will be different. In the above case, an offset will be applied to the Derived * pointer at runtime to generate the correct Base * pointer value.

 

The last example shows that an object (such as a Derived object) may have more than one address (for example, when the Base * Pointer Points to this object and the Derived * points to this object, there are two addresses ). This cannot happen in C, java, and C. In fact, this always happens when multiple inheritance is used, but it can also happen in single inheritance. This means that in C ++, you should avoid making assumptions about how something is laid out. For example,Convert the object addressThe char * pointer then performs pointer arithmetic operations on the pointer, which almost always produces undefined behavior..

 

However, note that the offset "sometimes" is required. The layout and address calculation methods of objects vary with the compiler. This means that just because you understand the layout and transformation of a platform does not mean that you can also work on other platforms. The world is filled with sad programmers who have learned from it.

 

3. Cast is easily misused-how is the invalid status generated?

 

One interesting thing about cast is that it is easy to write code that looks correct but is actually wrong. For example, many application frameworks require the implementation of virtual functions in the derived class to call the base class. Suppose we have a Window base class and a SpecialWindow derived class, both of which define the onResize virtual function. Further assume that the onResize function of SpecialWindow must first call the onResize function of Window. The following implementation method looks correct, but not actually:

 1 class Window {                       // base class 2  3 public:                                    4  5   6  7 virtual void onResize() { ... }           // base onResize impl 8  9 ...                                                   10 11 };                                                   12 13  14 15 class SpecialWindow: public Window {       // derived class16 17 public:                                                       18 19 virtual void onResize() {                             // derived onResize impl;20 21 static_cast<Window>(*this).onResize();     // cast *this to Window,22 23 24 // then call its onResize;25 // this doesn’t work!26 ... // do SpecialWindow-27 } // specific stuff28 ...29 30 31 };

 

I have marked the cast in the code in red. (Cast is a new style, and the conversion using the old style does not change the following fact ). As you expected, the Code converts * this into a window object. Therefore, when onResize is called, Window: onResize is triggered. What you may not think of is that it does not trigger the corresponding function on the current object. On the contrary,Transform* ThisCreates a temporary copy of the base class,OnResizeThis copy is triggered.! The above Code does not call Window: onResize on the current object and then executes the specified SpecialWindow action on this object -- before executing a specific action, the Window: onResize is called on the copy of the base class of the current object. If Window: onResize modifies the current object (it is very likely that since onResize is a non-const member function), the current object (Window object) will not be modified. The object is copied. However, if SpecialWIndow: onResize modifies the current object, the current object will be modified, and the above Code will leave an invalid status for the current object:The base class is not modified, but the derived class is modified..

The solution is to eliminate the use of cast. You don't want to fool the compiler to treat * this as a base class object. You want to call the base class version of onResize on the current object. Therefore, follow these steps:

1 class SpecialWindow: public Window {2 public:3 virtual void onResize() {4 Window::onResize(); // call Window::onResize5 ... // on *this6 }7 ...8 };

 

This example also shows that if you find that you want to use cast, it indicates that you may use the wrong method to apply it. This is also true when using dynamic_cast.

4. Dynamic_cast analysis 4.1 Dynamic_cast is slow

Before studying the design meaning of dynamic_cast, we can see that many implementations of dynamic_cast are very slow. For example, at least one common implementation is to some extent a string comparison based on the class name. If you are executing dynamic_cast on an object of a 4-layer Deep single inheritance system (that is, the general implementation mentioned above) each dynamic_cast may call strcmp up to four times to compare the class name. A deeper hierarchy of inheritance or a multi-hierarchy may increase the overhead. There is a reason for this (they must support dynamic link (dynamic linking )). Therefore,In additionCastAlways be alert to common problems, and be more sensitive to performance-sensitive code.Dynamic_castTo keep you updated.

4.2 two alternatives to Dynamic_cast

Dynamic_cast is required because you want to execute the operation of the derived class on top of the object you firmly believe it is a derived class, but you can only operate on this object through the base class pointer or base class reference. There are two common methods to avoid using dynamic_cast

First, the container is used to directly store the object pointer of the derived class (generally, the smart pointer is used, see Item 13). This eliminates the possibility of manipulating these objects through the base class interface. For example, in our window/SpecialWindow inheritance system, only SpecialWindows supports blink. Do not do this as follows:

 

 1 class Window { ... }; 2 class SpecialWindow: public Window { 3 public: 4 void blink(); 5 ... 6 }; 7  8 typedef                                                                                              // see Item 13 for info 9 10 std::vector<std::tr1::shared_ptr<Window> > VPW; // on tr1::shared_ptr     11 12 VPW winPtrs;                                                                                   13 14 ...                                                                                                       15 16 for (VPW::iterator iter = winPtrs.begin();                                          // undesirable code:17 18 iter != winPtrs.end();                                                                         // uses dynamic_cast19 20 ++iter) {                                                                                            21 22 if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))    23 24 psw->blink();                                                                                   25 26 }   

Instead, we use the following method:

 1 typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW; 2  3 VPSW winPtrs; 4  5 ... 6  7  8 for (VPSW::iterator iter = winPtrs.begin(); // better code: uses 9 iter != winPtrs.end(); // no dynamic_cast10 ++iter)11 (*iter)->blink();

 

Of course, this method does not allow you to store all possible windows in the same container. To achieve this goal, you may need multiple types of secure containers.

Second, provide virtual functions in the base class. For example, although only SecialWindos supports blink, you can declare a blink in the base class, but the default implementation is nothing:

 1 class Window { 2 public: 3 virtual void blink() {} // default impl is no-op; 4 ... // see Item 34 for why 5 }; // a default impl may be 6 // a bad idea 7 class SpecialWindow: public Window { 8 public: 9 virtual void blink() { ... } // in this class, blink10 11 ...12 13 // does something14 15 };16 17  18 19 typedef std::vector<std::tr1::shared_ptr<Window> > VPW;20 21  22 23 24 VPW winPtrs; // container holds25 // (ptrs to) all possible26 ... // Window types27 for (VPW::iterator iter = winPtrs.begin();28 iter != winPtrs.end();29 ++iter) // note lack of30 (*iter)->blink(); // dynamic_cast

 

The above two methods are not available in any situation, but in many cases they provide a feasible alternative for dynamic_cast. When they do what you want, you should embrace them.

4.3 do not use dynamic_cast in cascade Design

One thing you absolutely want to avoid is not to design the include cascade dynamic_cast, which is like the following:

 1 class Window { ... }; 2  3 ... 4  5 // derived classes are defined here 6  7 typedef std::vector<std::tr1::shared_ptr<Window> > VPW; 8  9  10 11 VPW winPtrs;12 13  14 15 ...16 17  18 19 for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)20 21  22 23 {24 25  26 27 if (SpecialWindow1 *psw1 =28 29  30 31 dynamic_cast<SpecialWindow1*>(iter->get())) { ... }32 33  34 35 else if (SpecialWindow2 *psw2 =36 37  38 39 dynamic_cast<SpecialWindow2*>(iter->get())) { ... }40 41  42 43 else if (SpecialWindow3 *psw3 =44 45  46 47 dynamic_cast<SpecialWindow3*>(iter->get())) { ... }48 49  50 51 ...52 53  54 55 }

 

The code generated by this implementation is large, slow, and fragile. Because every time the Windos class system changes, you need to check whether the above code needs to be updated. (For example, if a new derived class is added, the code above may need to add a new conditional branch ). Such code should be replaced by the virtual function-based design.

 

5. Hide the use of cast in the function interface

 

Good C ++ code seldom uses casts, but it is impractical to completely remove them. Cast such as converting Int to double is a reasonable application, although it may not be necessary. (You can declare a new double variable and initialize it with the value of x ). Like many potentially suspicious designs, you need to isolate cast usage as much as possible to hide it in interfaces invisible to callers.

 

6. Conclusion:
    • Avoid using cast, especially when using dynamic_cast in performance-sensitive code. If cast is required for a design, first try whether to design an alternative solution that does not require cast.
    • When you must use casting, try to hide it in the function. Customers can call this function to avoid using casts in their own code.
    • C ++-style cast is preferred instead of the old-style casts. Because they are easy to see, they do more clearly.

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.