Type Conversion and Casting functions

Posted by Grant6899 on December 19, 2017

Type Conversion

A conversion operator is a special kind of member function that converts a value of a class type to a value of some other type. A conversion function typically has the general form:

operator type() const;
class SmallInt {
public:
	SmallInt(int i = 0): val(i){
		if (i < 0 || i > 255)
			throw std::out_of_range("Bad SmallInt value");
	}
	operator int() const { return val; }
private:
	std::size_t val;
};
  • Conversion operators have no explicitly stated return type and no parameters
  • They must be defined as member functions
  • Conversion operations ordinarily should not change the object they are converting, usually should be defined as const members

Here’s a bad example:

class SmallInt;
operator int(SmallInt&); // error: non-member

class SmallInt {
public:
	int operator int() const; // error: return type
	operator int(int = 0) const; // error: parameter list
	operator int*() const { return 42; } // error: 42 is not a pointer
};

If the conversion operator is explicit, we can still do the conversion. However, with one exception, we must do so explicitly through a cast:

class SmallInt {
public:
	// the compiler won't automatically apply this conversion
	explicit operator int() const { return val; }	// other members as before
};

To use it:

SmallInt si = 3; // ok: the SmallInt constructor is not explicit
si + 3; // error: implicit conversion is required, but operator int is explicit
static_cast<int>(si) + 3; // ok: explicitly request the conversion

Explicit and implicit constructors can convert other types to the current class. Vice versa, explicit and implicit conversion operator can convert current class to other types.

Conversion to bool is usually intended for use in conditions. As a result, operator bool ordinarily should be defined as explicit.

Casting functions

C-style cast

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

Fucntion-style cast

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

C++’s casting functions

  • const_cast
      const_cast<T>(expression)
    

    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
      dynamic_cast<T>(expression)
    

    dynamic_cast is primarily used to perform “safe downcasting,” i.e., 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
      reinterpret_cast<T>(expression)
    

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

  • static_cast
      static_cast<T>(expression)
    

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

Rules and Conventions

    • Easier to identify (both for humans and “grep”)
    • The more narrowly specified purpose of each cast, the easier for compilers to diagnose usage errors

    The only case that an old-style cast is preferable is when calling an explicit constructor to pass an object to a function:

      class Widget {
      public:
      explicit Widget(int size);
      ...
      };
      void doSomeWork(const Widget& w);
      doSomeWork(Widget(15));
    
  • Type conversions often lead to code that is executed at runtime

    The behavior of the generated code varies across different platforms and compilers

  • Slicing function calling

    Wrong example:

      class Window { // base class
      public:
          virtual void onResize() { ... } // base onResize impl
      ...
      };
        
      class SpecialWindow: public Window { // derived class
      public:
          virtual void onResize() { // derived onResize impl;
          static_cast<Window>(*this).onResize(); // cast *this to Window, then call its onResize; this doesn’t work!
          ... // do SpecialWindow- specific stuff
      };
    

    The cast creates a new, temporary copy of the base class part of *this, then invokes onResize on the copy.

    Correct way:

      class SpecialWindow: public Window { // derived class
      public:
          virtual void onResize() { // derived onResize impl;
          Window::onResize(); // call Window::onResize on *this
          ... // do SpecialWindow- specific stuff
      };
    
  • Avoid using dynamic_cast because it’s expensive

    The need for dynamic_cast generally arises because you want to perform derived class operations on what you believe to be a derived class Implementations object, but you have only a pointer- or reference-to-base through which to manipulate the object.

    The best solution is to use containers that store pointers altogether with virtual functions provided in classes pointed to. By this way, dynamic binding can work seamlessly with those pointers and you don’t need to care about casting at all. You can also save all objects into the same container meanwhile.

      class Window {
      public:
          virtual void blink() {} // default impl is no-op;
          ... // see Item34 for why
      }; // a default impl may be a bad idea
    	
      class SpecialWindow: public Window {
      public:
          virtual void blink() { ... } // in this class, blink
          ... // does something
      };
    	
      typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
      VPW winPtrs; // container holds
      // (ptrs to) all possible Window types
      for (VPW::iterator iter = winPtrs.begin();iter != winPtrs.end();++iter) // note lack of dynamic_cast
      	(*iter)->blink();