Author : G. Bowden Wise
Page : << Previous 2 Next >>
casts, was to gain access to a private base class of a derived class. Consider this hierarchy:
class Base
{
public:
Base() : _data(999) {}
int Data() const {return _data;}
private:
int _data;
};
class Derived : private Base
{
public:
Derived () : Base() {}
};
Derived* d1 = new Derived;
Normally, you should not be able to access Data() through the pointer d1. However, using an old C-style cast, we can:
Base* b1 = (Base*) d1;
int i = b1->Data(); // works!
The good news is that if you attempt to use static_cast:
Base* b1 = static_cast<Base*>(d1);
the compiler will correctly report that Base is inaccessible because it is a private base class.
Another unfortunate hole created in the type system by the old C-style casts results with incomplete types. Consider:
class X; // incomplete
class Y; // incomplete
The old C-style casts, let us cast from one incomplete type to another! Here is an example:
void f(X* x)
{
Y* y = (Y*) x; // works!
}
Thankfully, this hole has also been plugged by static_cast:
void f(X* x)
{
Y* y = static_cast<Y*> x; // fails
}
The const_cast Operator
The const_cast operator takes the form
const_cast<T> (expr)
and is used to add or remove the ``const-ness'' or ``volatile-ness'' from a type.
Consider a function, f, which takes a non-const argument:
double f( double& d );
However, we wish to call f from another function g:
void g (const double& d)
{
val = f(d);
}
Since d, which is const and should not be modified, the compiler will complain because f may potentially modify its value. To get around this dilemma, we can use a const_cast:
void g (const double& d)
{
val = f(const_cast<double&>(d));
}
which strips away the ``const-ness'' of d before passing it to f.
Another scenario where const_cast is useful is inside const functions. Remember that when you make a member function const, you are telling your users (and the compiler) that calling this function will not change the value of the object. However, in some cases, we find that it is sometimes still necessary to change the value of some internal data members inside a function that is const. For example, consider class B:
class B
{
public:
B() {}
~B() {}
void f() const;
private:
int _count;
};
Suppose that, f(), which is declared to be const, must modify _count whenever it is called:
void B::f() const
{
_count += 1;
}
The compiler will not allow _count to be changed because the function is const. Just how does the compiler perform this magic? Turns out that the type of the internal this pointer helps the compiler perform this check.
Every non-static member function of a class C has a this pointer. For non const member functions of class C, this has type
C * const
This means that this is a constant pointer. In other words, you cannot change what the pointer this points to, after all, that would be disastrous, wouldn't it? However, you can still change what ever this points to (i.e., you can change data members of class C).
For const member functions of class C, this has a type of
const C * const
Not only is this a constant pointer but also what is pointed to is constant. So the data members of C may not be changed through the this pointer. This is how the compiler ensures that you do not modify data members inside const functions.
Examining the member function B::f again, the statement _count is actually interpreted as this->_count. But since this has type const B * const, it cannot be used to change the value of _count so the compiler reports an error.
We can, however, use const_cast to cast away the ``const-ness'' of this:
void B::f() const
{
B* const localThis =
const_cast<B* const>(this);
localThis->_count += 1;
}
Actually, you should not be casting away the ``const-ness'' of this using const_cast. C++ now has the keyword mutable for those data members whose value may be changed by const functions. By declaring _count as:
mutable int _count;
We can use the original implementation of B::f without casting away the ``const-ness'' of this.
const_cast can also be used to strip away the ``volatile-ness'' of an object in a similar manner. You cannot use const_cast for any other types of casts, such as casting a base class pointer to a derived class pointer. If you do so the compiler will report an error.
The dynamic_cast Operator
The dynamic_cast operator takes the form
dynamic_cast<T> (expr)
and can be used only for pointer or reference types to navigate a class hierarchy. The dynamic_cast operator can be used to cast from a derived class pointer to a base class pointer, cast a derived class pointer to another derived (sibling) class pointer, or cast a base class pointer to a derived class pointer. Each of these conversions may also be applied to references. In addition, any pointer may also be cast to a void*.
The dynamic_cast operator is actually part of C++'s run-time type information or RTTI sub-system. As such, it has been provided for use with polymorphic classes - those classes which have at least one virtual function. Use static_cast to perform conversions between non-polymorphic classes.
All of the derived to base conversions are performed using the static (compile-time) type information. These conversions may, therefore, be performed on both non-polymorphic and polymorphic types. These conversions will produce the same result if they are converted using a static_cast. These conversions are fairly straightforward so we won't discuss them further.
Conversions down the hierarchy from base to derived or across a class hierarchy rely on run-time type information and can only be performed on polymorphic types. Such conversions can now be performed safely since dynamic_cast will indicate whether the conversion is successful. When performing a dynamic_cast on a pointer, a null pointer is returned when the cast is unsuccessful. When a reference is being cast, a Bad_cast exception is thrown.
Let's look at the power of run-time type conversions by revisiting the bank account hierarchy introduced above with static_cast. Recall that when acct does not actually point to a SavingsAcct object the result of the static_cast is undefined. Since BankAcct has at least one virtual function it is a polymorphic class. We can use a dynamic_cast instead to check that the cast was successful:
void f (BankAcct* acct)
{
SavingsAcct* d1 =
dynamic_cast<SavingsAcct*>(acct);
if (d1)
{
// d1 is a savings account
}
}
Let's expand our bank account hierarchy to include a few more types of accounts, such as a checking account and a money market account. Let's suppose we also want to extend the functionality so that we can credit the interest for all savings and money market accounts in our database. Suppose further that BankAcct is part of a vendor library; we are not able to add any new members functions to BankAcct since we do not have the source code.
Clearly, the best way to incorporate the needed functionality would be to add a virtual function, creditInterest() to the base class, BankAcct. But since we are not able to modify BankAcct we are unable to do this. Instead, we can employ a dynamic_cast to help us.
We add the method creditInterest() to both SavingsAcct and MMAcct classes. The resulting class hierarchy looks like:
class BankAcct { /* ... */ }
class SavingsAcct : public BankAcct
Page : << Previous 2 Next >>