Casting in C++, Part 2

Within the first part of the article we have seen the existing type of casts and we have analyzed the c++ style operators for static cast and dynamic cast. We will now continue with the const cast and reinterpret cast operator and finish the topic with some more advanced topics like comparison between dynamic and static cast and the disadvantages of c style casts.

const_cast

The const cast operator typically used to cast away the constness of an object. It is the only c++ style cast that can do this. Const cast is considered safer than simple type casting because it won’t happen if the type of cast is not same as the type of the original object.

Like any other cast operator, the const cast should be used wisely. A constant object is normally explicitly constant to avoid misusing and possible application errors or undefined behavior. The following example shows a possible use case. Let´s say we have an object which allows to execute data queries. Such a query should analyze the data only and therefore the according method is defined as const, so the this-pointer is const and the data of the object cannot be changed within the method.

class DataQuery
{
public:
  DataQuery()
  {
  };

  int ExecuteQuery(const std::string query) const
  {
    return 42;
  }
};

Now you want to extend the actual implementation with a query counter. This may be easy as you can add a new object member and increase it by every call to the query method. But unfortunately, you are not allowed to change the object interface (for example to avoid conflicts with clients). As the method is defined as const and therefore the this-pointer is const, you cannot increase your member variable. In such a case, you can use the const cast operator to cast away the constness of the this-pointer. The following source code shows the adapted example.

class DataQuery
{
public:
  DataQuery() : mCounter{ 0 }
  {
  };

  int ExecuteQuery(const std::string query) const
  {
    (const_cast<DataQuery*>(this))->mCounter++;

    return 42;
  }

private:
  int mCounter;
};

Of course, in such a case you may ask whether this is a clean solution of the issue or just a workaround to bypass an environment limitation, in this case the fixed interface. In my opinion, a const cast is always a workaround. A clean architecture and implementation should not have a need to use a const cast operator.

But why may it sometimes by dangerous to cast away the constness and sometimes it is ok? And how can you recognize and distinguish these two situations? The following two examples show an interesting aspect which may help you to answer these questions. Let´s say we have a variable and a constant pointer to this variable. That’s typical in many situations, for example in the above example with the query counter we had have an internal data member and a constant pointer to the data (the this-pointer). Furthermore, we have a function which changes data and therefore you have a non-const pointer to the data. The following source code shows a possible implementation of such a use case.

void fun(int* pValue)
{
  *pValue = *pValue + 10;
}

int _tmain(int argc, _TCHAR* argv[])
{
  int value = 10;

  const int *pValue = &value;
  int* pValueNotConst = const_cast<int*>(pValue);
  fun(pValueNotConst);

  std::cout << value;
  return 0;
}

What do you think: Is it allowed to cast away the constness in this case? Please think about this question for a short moment…

And now we will do a little modification and define the origin integer value as const too.

void fun(int* pValue)
{
  *pValue = *pValue + 10;
}

int _tmain(int argc, _TCHAR* argv[])
{
  const int value = 10;

  const int *pValue = &value;
  int* pValueNotConst = const_cast<int*>(pValue);
  fun(pValueNotConst);
  
  std::cout << value;
  return 0;
}

What do you think now: Will this change your opinion whether the cast is allowed or not? Is it allowed in both cases, just in one or maybe in none of the two examples?

Within the first of the two examples the origin variable is not const. You can modify the value and therefore it is fine to cast away the constness of the pointer to change the data of the variable. In the second example, the origin data is const and therefore you should never change it. If you bypass this limitation and try to change the value, you may result in undefined behavior – in the above example the value of the variable is still “10” even after function call.

Of course, the implementation of the first example is dangerous too. Even if it works now it may be an issue in future, for example if someone changes the constness of the origin variable. In such cases the variable and the client code with the cast are normally not within three lines of code inside a method. Instead they are often far away from each other, in different classes and modules. Such a change will result in annoying and costly troubleshooting.

reinterpret_cast

By using the reinterpret cast operator the given data is interpreted as it has the new type. This cast will not do a type convert. It will read the memory you passed in a different way. You give it a memory location and you ask it to read that sequence of bits as if it had the new type. Therefore, it can only be used with pointers and references. Reinterpret cast is intended for low-level casts that yield implementation dependent results, for example casting a pointer to an int. Such casts should be rare outside low-level code.

The following code shows an example were the given unsigned short integer value is reinterpreted as signed short integer.

int _tmain(int argc, _TCHAR* argv[])
{
  unsigned short int value1 = 30;
  unsigned short int value2 = 40000;
  
  short int value3 = *reinterpret_cast<short int*>(&value1);
  short int value4 = *reinterpret_cast<short int*>(&value2);

  std::cout << value3 << std::endl;   // value is like expected
  std::cout << value4 << std::endl;   // may result in undefined behaviour

  return 0;
}

Such a reinterpretation may work or it may result in undefined behavior. Within this example you have such a situation in case the value range of the source data type exceeds the value range of the target data type. Therefore, if you use reinterpret cast, you must know what you are doing as the compiler cannot detect such logical issues. Reinterpret cast is very dangerous and why it should not be used in this type of cases. You should only use it when you have a pointer and you need to read that memory location in a certain way and you know that the memory can be read in that way.

dynamic vs. static cast

In some situations, you may not be sure whether to use a dynamic cast or a static cast. The advantage of using a dynamic cast is that it allows to check whether a conversion has succeeded during run-time. The disadvantage is that there is a performance overhead associated with this check.

If you want to cast a derived class to its base class, a dynamic cast as well as a static cast will give you the right result. As the dynamic cast coms with a performance overhead you should use a static cast in this case.

If you want to cast a base class to its derived class the conversion may be succeed or fail depending whether your class is of the expected type or not. A dynamic cast will check whether the conversion is possible or not. Therefore, you should prefer a dynamic cast in this situation as a static cast may result in undefined behavior.

The following example contains both situations, a derived-to-base cast and a base-to-derived cast.

class Animal
{
public:
  virtual void Print() const
  {
    std::cout << "animal" << std::endl;
  };
};

class Bird : public Animal
{
public:
  void Print() const
  {
    std::cout << "bird" << std::endl;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  Bird bird = Bird();
  
  Animal animal1 = static_cast<Animal>(bird);  // OK
  Animal* animal2 = dynamic_cast<Animal*>(&bird);  // OK, but slower

  Bird* bird1 = static_cast<Bird*>(animal2);   // may result in undefined behavior
  Bird* bird2 = dynamic_cast<Bird*>(animal2);  // checks whether conversion is possible
    
  return 0;
}

Disadvantages of c style casts

As mentioned before you should avoid c style casts. They are not bad at all or will inevitably lead to errors, but compared to the new c++ style cast they have some disadvantages and therefore a higher possibility of misuse. Following I want to show some examples to explain the disadvantages of c style casts.

We will start with a typical situation: existing code will be changed. Of course, this will happen often in daily business and unfortunately such changes, even little ones, may introduce new errors. The following example shows a typical situation where a function “foo” is called which calls another function “bar”. As bar expects a derived class we must convert the parameter. Later, the function foo must be changed a little bit and the parameter should be passed as const parameter. The function “foo2” will show this change. Unfortunately, by using the c style cast, we will cast the constness of the parameter away. Depending on the implementation details of “foo2” and “bar” this may lead to unexpected behavior.

class Animal{};
class Bird : public Animal{};

void Bar(Bird* bird)
{
}

// origin function with c style cast
void Foo(Animal* animal)
{  
  Bird* bird = (Bird*)animal;
  Bar(bird);
}

// new function with c style cast
void Foo2(const Animal* animal)
{
  Bird* bird = (Bird*)animal;
  Bar(bird);
}

int _tmain(int argc, _TCHAR* argv[])
{
  Bird bird = Bird();  
  Foo(&bird);
  
  return 0;
}

If we have the same situation but implement it by using a c++ style cast, the compiler will detect such an issue and you get a compiler error. The following code shows the same example with a c++ style cast.

class Animal
{ 
public:
  virtual ~Animal(){}; 
};
class Bird : public Animal{};

void Bar(Bird* bird)
{
}

// origin function with c++ style cast
void Foo(Animal* animal)
{  
  Bird* bird = dynamic_cast<Bird*>(animal);
  Bar(bird);
}

// new function with c++ style cast
void Foo2(const Animal* animal)
{
  Bird* bird = dynamic_cast<Bird*>(animal);  // compiler error
  Bar(bird);
}

int _tmain(int argc, _TCHAR* argv[])
{
  Bird bird = Bird();  
  Foo(&bird);
  
  return 0;
}

In c we have no classes. But we can use c style cast to convert base-to-derived class types and vice versa. How is this possible? If you think about this question you found another issue regarding c style cast. As seen within the section “dynamic vs. static cast” a derived-to-base cast is harmless. That’s also true if you use c style casts. But a base-to-derived class may result in undefined behavior. If you use a c style cast in this case, it behaves like a reinterpret cast. And unfortunately, this will often result in undefined behavior, as many developers will expect that the cast is done well and they not expect a reinterpret cast in this situation. The following example shows such a situation.

class Animal
{
public:
  virtual void Print() const
  {
    std::cout << "animal" << std::endl;
  };
};

class Bird : public Animal
{
public:
  void Print() const
  {
    std::cout << "bird" << std::endl;
  }
};

class Fish : public Animal
{
public:
  void Print() const
  {
    std::cout << "fish" << std::endl;
  }
};

void Foo(const Animal* animal)
{
  animal->Print();

  const Bird* bird = (Bird*)(animal);
  if (bird)
  {
    bird->Print();
  }

  const Fish* fish = (Fish*)(animal);
  if (fish)
  {
    fish->Print();
  }
}

int _tmain(int argc, _TCHAR* argv[])
{
  Bird bird = Bird();
  Fish fish = Fish();

  Foo(&bird);
  Foo(&fish);

  return 0;
}

If you implement the above example by using c++ style casts you will explicitly select one of the four cast operators. In this case the dynamic cast will be your choice. This explicit operator selection is a big advantage of c++ style casts over c style casts. It will make the code more robust, as the compiler is now able to detect programming errors, and it will make the code more readable as it now contains an explicit information what we want to do.

If you use c style casts, you sign a contract with your compiler and promise: “I know what I am doing”. This may be fine four you and I’m sure you really know what you are doing. But unfortunately, in the same moment you add something more to this contract: “I also know what other programmers are doing and I know they know what I am doing.” This sound like a strange promise which cannot be fulfilled. But as you are most often not the only developer ever touching this code, you will implicitly give this promise by using c style casts.

Summary

Using the four c++ style casting operator’s makes the code more readable and more maintainable. It makes the logic behind the code more explicit. And it makes the code less error-prone by having the compiler catch errors either as you’re making them or later as you go back and change old code.

But on the other hand, you should not often have to use cast operators as they indicate software design issues.

In summary, I want to define three simple guidelines:

First guideline: Do not use casts. Instead check whether the software design is suitable.

Second guideline: If you must cast, use the c++ style cast operators.

Third guideline: Avoid dynamic cast in performance sensitive code.

Advertisements
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

w

Verbinde mit %s