Chapter Six

Conversions

It can be difficult to understand C++-code that uses implicit type conversions between otherwise unrelated types. A number of techniques can be used to prevent such problems. Some conversions are so dangerous that most compilers will give you a warning. We will show you how to avoid the dangers involved by providing a few guidelines.

RULES
AND
RECOMMENDATIONS

Rec 6.1 Prefer explicit to implicit type conversions.

Rec 6.2 Use the new cast operators ( dynamic_cast, const_cast, reinterpret_cast and static_cast ) instead of the old-style casts, unless portability is an issue.

Rec 6.3 Do not cast away const.

Rule 6.4 Declare a data member as mutable if it must be modified by a const member function.

Rec 7.18 - Rec 7.19 , conversion functions.

Rec 6.1 Prefer explicit to implicit type conversions.

Most conversions are bad in some way. They can make the code less portable, less robust and less readable. It is therefore important to only use explicit conversions. Implicit conversions are almost always bad.

It is common to use different integral types in a program. It can be dangerous to mix different types, since the size and layout of these types varies. A value that may fit in e.g. a short on one platform, is truncated on another platform. By always having explicit conversions, it is much easier to find potentially dangerous code.

It is also common that a class provides an implicit conversion to its representation. This makes it possible to pass an object as argument to functions expecting direct access to the representation. If such conversions are needed, we do not recommend you to have a conversion operator function to do the job. You should instead have a member function that does the conversion for you.

Explicit conversions

 
const unsigned large = 456789;

// Potentially dangerous conversion
const int      size  = (int)large;    

Conversion of string object to const char*

It is common that a string class provides an implicit conversion to a const char* . This makes it possible to pass a string object as argument to functions expecting such a pointer.

 
class DangerousString
{
   public:
      // ...
      DangerousString(const char* cp);
      // ...
      operator const char*() const;   // Not recommended
      const char*    cStr() const;    // Recommended
      // ...
};

If your string class provide both a conversion operator member function and an ordinary member function, you should always use the latter. If only a conversion operator function is provided, you should only use explicit conversions.

 
EmcStack<const char*> stack;
stack.push("one");

DangerousString two("two");

// Not recommended to store the result of a conversion.
// Implicit conversion is not recommended.
stack.push(two);                // Implicit conversion

DangerousString three("three");
// Explicit conversion is better than 
// implicit conversion.
stack.push((const char*)three); // Explicit conversion

DangerousString four("four");
// Member function call is better than 
// conversion operator function call.
stack.push(four.cStr());        // Member function call

Rec 6.2 Use the new cast operators (dynamic_cast, const_cast, reinterpret_cast and static_cast) instead of the old-style casts, unless portability is an issue.

There are many ways to convert values in C++; the traditional C cast notation, the functional notation and new-style casts. The first two are explained in most introductory C++ books. A new-style cast means that one of the four new cast operators:

is being used. If your compiler supports the new cast operators you should use them instead of the traditional cast operators, since they give the user a way to distinguish between different types of casts.

A good thing about these operators is that their behavior is well-defined in situations where the behavior of an ordinary cast is undefined, or at least ambiguous. They cannot remove all dangers involved in type conversions, but they are far better than the traditional cast syntax.

In order to use them, you must understand when each one of them is appropriate.

A static cast is similar to an ordinary cast except that it will not allow you to cast away constness or cast between unrelated types. You can replace all implicit conversions with static_cast expressions.

Whenever you can make an implicit conversion from one type to another, you can make a static_cast in the opposite direction. You can, for example, use static casts for base to derived conversions if the base class is non-virtual.

The operator const_cast is solely used for casting away const.

The operator reinterpret_cast is used when casting between unrelated types, e.g. when casting an int* to a char* .

The operator dynamic_cast checks the type of its operand at run-time. It is similar to a static_cast , but it is more safe. It can only be used for types with run-time type information, i.e. classes with at least one virtual member function, also called polymorphic classes. It also allows base to derived conversions when the base class is virtual. Since there is a run-time penalty for using dynamic_cast instead of static_cast , you should only use it when it is absolutely necessary.

A problem with these operators is that they are not yet supported by all compilers. Therefore, if you anticipate porting your code to another environment, you should consider avoiding them for portability reasons.

Using static_cast

 
unsigned large = 456789;
int size = static_cast<int>(large);

EmcStack<const char*> stack;
EmcString three("three");

// Not recommended to store the result of a conversion.
// static_cast is better than old-style cast.
stack.push(static_cast<const char*>(three)); 

New style casts

 
class B
{
   public:
      // ...
      virtual ~B();
};

class D : virtual public B
{
   public:
      // ...
      virtual ~D();
};

class E
{
   public:
      // ...
      virtual ~E();
};

D* dynamicCast(B* b)
{
   // Must use dynamic_cast when base class is virtual.
   return dynamic_cast<D*>(b); 
}

D* constCast(const D* d1)
{
   // Should use const_cast when casting away const.
   return const_cast<D*>(d1);  
}

E* reinterpretCast(D* d)
{
   // Should use reinterpret_cast when casting pointer
   // to pointer of unrelated type.
   return reinterpret_cast<E*>(d); 
}

Rec 6.3 Do not cast away const.

You should not cast away the constness of objects. There are however a few rare cases where casting away constness is permitted, such as if you need to use a function which has incorrectly specified a parameter as non-const even if it does not modify it. If you have been passed a const object, and need to pass it to the function which takes a non-const object as parameter, then you are forced to choose between two evils. Either you modify your own function so that you will be passed a non-const object. This is not fair, since this will only pass the problem to your user. Instead you should solve the problem by maintaining your const correct interface and cast away the constness of the object before you pass it to the function you need to use.

There are other problems with casting away const, such as the fact that const objects might reside in write protected memory. It is undefined what happens if you change such an object, but probably the run-time system will report an error.

Casting away const

 
// NOT RECOMMENDED
// Parameter should be of type const EmcString&
void addToFileList(EmcString& s); // does not modify s

void addFiles(const EmcArray<EmcString>& s)
{
   size_t max = s.size();
   for(size_t i = 0; i < max; i++)
   {
      // casting away const is NOT RECOMMENDED
      // s[i] returns const EmcString&
      addToFileList((EmcString&) s[i]); 
      // ...
   }
}

Object in write-protected memory

 
// ci may be in write-protected memory
const int ci = 22;

int* pi = (int*) &ci; // NO: Const cast away

// reading write-protected memory?
int i = *pi;          // OK

// writing into write-protected memory?
*pi = 7;              // NO: This MAY fail!!!

Rule 6.4 Declare a data member as mutable if it must be modified by a const member function.

If an object caches computed values for the sake of efficiency, such data members should be declared mutable since that makes them modifiable inside const member functions.

Class with mutable data member

 
class EmcMatrix
{
   public:
      double determinant() const;
      // ...
   private:
      mutable bool   isDirtyM;         // mutable
      mutable double detM;             // mutable
      double calculateDeterminant() const;
      // ...
};

double EmcMatrix::determinant() const
{
   if(isDirtyM)
   {
       // OK, access to mutable data members
       detM = calculateDeterminant();
       isDirtyM = false;
   }
   return detM;
}

The member function determinant() was declared const even though it changed data members of the class. This was made possible by declaring these data members as mutable .

If your compiler does not support mutable data members, then the best solution is to cast away const inside the function, and add a comment to show other readers of the code that you had no other option in order to keep the interface const-correct.