27 Minimize casting

The rules of C++ are designed to guarantee that type errors are impossible. In theory , if your program compiles cleanly, it is not trying to perform any unsafe or nonsensical operations on any objects.

Unfortunately, casts subvert the type system.That can lead to all kinds of trouble, some easy to recognize, some extraordinarily subtle. In C++, casting is a featuring that we should approach with great respect.

C-style casts look like this :

(T) expression
T(expression)

There is no difference in meaning between these forms; it is purely a matter of where you put the parentheses. I call these two forms old-style casts.

C++ also offers four new cast forms (new-style or C++ style casts) :

const_cast<T> (expression)
dynamic_cast<T> (expression)
reinterpret_cast<T> (expression)
static_cast<T> (expression)

Each serves a distinct purpose :

  • const_cast is typically used to cast away the constness of objects. It is the only C++-style cast that can do this.

  • dynamic_cast is primarily used to perform "safe downcasting", to determine whether an object is of a particular type in an inheritance hierarchy.It is the only cast that cannot be performed using the old-style syntax. It is also the only cast that may have a significant runtime cost.

  • reinterpret_cast is intended for low-level casts that yield implementation-dependent results, e.g., casting a pointer to an int. Such casts should be rare outside low-level code.

  • static_cast can be used to force implicit conversions.(e.g., non-const object to const object), int to double. It can also be used to perform the reverse of many such conversions(void* pointers to typed pointers), though it cannot cast from const to non-const objects.

The new forms are preferable because

  1. they are much easier to identify in code, thus simplifying the process of finding places in the code where the type system is being subverted.
  2. The more narrowly specified purpose of each cast makes it possible for compilers to diagnose usage errors. For example, if you try to cast away constness using a new-style cast other than const_cast , your code won't compile.

Many programmers believe that casts do nothing but tell compilers to treat one type as another, but this is mistaken. Type conversions of any kind often lead to code that is executed at runtime. For example :

int x, y;
...
double d = static_cast<double>(x) / y;

the cast of int x to a double almost certainly generates code, because on most architectures, the underlying representation for an int is different from that for a double, let us see another example :

class Base{...};
class Derived : public Base{...};
Derived d;
Base *pd = &d; // implicit convert Derived* => Base*

Here we are just creating a base class pointer to a derived class object, but sometimes, the two pointer values will not be the same. When that's the case , an offset is applied at runtime to the Derived* pointer to get the correct Base* pointer value.

This example demonstrates that a single object might have more than one address. That would happen in C++. In fact , when multiple inheritance is in use, it happens virtually all the time, but it can happen under single inheritance, too. Among other things, that means you should generally avoid making assumptions about how things are laid out in C++, and you should certainly not perform casts based on such assumptions. For example, casting object addresses to char* pointers and then using pointer arithmetic on them almost always yields undefined behavior.

The way object are laid out and the way their addresses are calculated varies from compiler to compiler. That means that just because your "I know how things are laid out" casts work on one platform does not mean they will work on others.

An interesting thing about casts is that it is easy to write something that looks right but is wrong. Many application frameworks , for example, require that virtual member function implementations in derived classes all their base class counterparts first. Suppose we have a Window base class and a SpecialWindow derived class, both of which define the virtual function onResize. Further suppose that SpecialWindow's onResize is expected to invoke Window's onResize first. Here is a way to implement this that looks like it does the right thing, but does not :

class Window{
public: 
  virtual void onResize(){...}
};
class SpecialWindow : public Window {
public:
  virtual void onResize() {
  static_const<Window>(*this).onResize();
}
};

In this code, the code casts *this to a Window. The resulting call to onResize therefore invokes Window::onResize. What you might not expect is that it does not invoke that function on the current object!
Instead , the cast creates a new, temporary copy of the base class of *this, then invokes onResize on the copy object ! So if it makes some change on the object in the function onResize, it would only modify the object which it is invoked, the current object would not be modified. So if Window::onResize modifies the object, it would not make sense on the SpecialWindow object. Meanwhile, it SpecialWindow::onResize modifies the object , however, the current object will be modified, leading to the code will leave the current object in an invalid state, one where base class modifications have not been made, but derived class ones have been.

class Window{
    public:
        Window()
        {
            this->length = 100;
            this->width = 500;
        }
        virtual void onResize()
        {
            this->length = this->length * 2;
            this->width = this->width * 2;
        }
        virtual void show()
        {
            cout << "length is " << length << '\t' << "width is " << width << endl;
        }
        int length;
        int width;
};
class SpecialWindow : public Window{
    public:
        virtual void onResize()
        {
            static_cast<Window>(*this).onResize();
        }

};
int main()
{
    Window w;
    w.show();
    w.onResize();
    w.show();
    cout << "=======" << endl;
    SpecialWindow a;
    // a.show();
    a.onResize();
    a.show();
}

And the result is :

length is 100   width is 500
length is 200   width is 1000
=======
length is 100   width is 500

The solution is to eliminate the cast , replacing it with what you really want ty say. you want to call the base class version of onResize on the current object :

class SpecialWindow:public Window{
public:
  virtual void onResize(){
  Window::onResize();
  }
};

The need for dynamic_cast generally arises because you want to perform derived class operations on what you believe to be a derived class object, but you have only a pointer or reference -to-base through which to manipulate the object. There are two general ways to avoid this problem :
First , use containers that store pointers

class Window { ... };

class SpecialWindow: public Window {
public:
  void blink();
  ...
};
typedef                                            // see Item 13 for info
  std::vector<std::tr1::shared_ptr<Window> > VPW;  // on tr1::shared_ptr

VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin();         // undesirable code:
     iter != winPtrs.end();                        // uses dynamic_cast
     ++iter) {
  if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))
     psw->blink();
}

However, try to do this instead :

typedef vector<std::tr1::shared_ptr<SpecialWindow>> vpsw;
vpsw winPtrs;

for(vpsw::iterator it = winPtrs.begin(); it != winPtrs.end(); it++)
(*iter) -> function();

Of course , this approach won't allow you to store pointers to all possible Window derivatives in the same container.

An alternative that will let you manipulate all possible Window derivatives through a base class interface is to provide virtual functions in the base class that let you do what you need.

Conclusion

-Avoid casts whenever practical, especially dynamic_cast in performance-sensitive code. If a design requires casting, try to develop a cast-free alternative.

-When casting is necessary , try to hide it inside a function. Clients can then call the function instead of putting casts in their own code.

  • Prefer C++-style casts to old-style casts. They are easier to see, and they are more specific about what they do.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容