Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] نسخه متنی

This is a Digital Library

With over 100,000 free electronic resource in Persian, Arabic and English

Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] - نسخه متنی

Bjorn Karlsson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید

polymorphic_cast


Header: "boost/cast.hpp"


Polymorphic conversions in C++ are performed via dynamic_cast. A feature of dynamic_cast, which is sometimes also the cause of erroneous code, is that it behaves differently depending on the type with which it is used. dynamic_cast tHRows an exceptionstd::bad_castif the conversion is not possible when used on a reference type. The reason for the exception is simple. There is no such thing as a null reference in C++, so either the conversion succeeds and the result is a valid reference or it fails and you get an exception instead. Of course, when using dynamic_cast to convert a pointer type, failure is indicated by returning the null pointer.

dynamic_cast's different behavior depending on whether pointer or reference types are used is a valuable property, because it allows the programmer to express intent. Typically, if a failed conversion doesn't constitute a logical error, the pointer conversion is used, and if it is an error, the reference version is used. Unfortunately, the difference is quite subtleit boils down to an asterisk or an ampersandand it isn't always a natural choice. What if a failed cast to a pointer type is an error? To make that clear by having an exception thrown automatically, and to make the code consistent, Boost offers polymorphic_cast. It always throws a std::bad_cast exception if the conversion fails.

In The C++ Programming Language 3rd Edition, Stroustrup has the following to say about dynamic_cast with pointer types, and the fact that it can return the null pointer:

"Explicit tests against 0 can beand therefore occasionally will beaccidentally omitted. If that worries you, you can write a conversion function that throws an exception in case of failure."

polymorphic_cast is precisely that conversion function.

Usage


polymorphic_cast is used just like dynamic_cast, except (pun intended) that it always throws a std::bad_cast on failure to convert. Another feature of polymorphic_cast is that it is a function, and can be overloaded, if necessary. As a natural extension to our C++ vocabulary, it makes code clearer and casts less error prone. To use it, include the header "boost/cast.hpp". The function is parameterized on the type to convert to, and accepts one argument to be converted.


template <class Target, class Source>
polymorphic_cast(Source* p);

It should be mentioned that there is no version of polymorphic_cast for reference types. The reason for this is that the implementation would do exactly what dynamic_cast already does, and there is no need for polymorphic_cast to duplicate existing functionality of the C++ language. The following example shows the syntactic similarity with dynamic_cast.

Downcast and Crosscast


There are two typical scenarios when using dynamic_cast or polymorphic_cast is appropriate: when downcasting from a base class to a derived class or when crosscasting, which means casting from one base class to another. The following example shows both types of casts using polymorphic_cast. There are two base classes, base1 and base2, and a class derived that inherits publicly from both of the base classes.


#include <iostream>
#include <string>
#include "boost/cast.hpp"
class base1 {
public:
virtual void print() {
std::cout << "base1::print()\n";
}
virtual ~base1() {}
};
class base2 {
public:
void only_base2() {
std::cout << "only_base2()\n";
}
virtual ~base2() {}
};
class derived : public base1, public base2 {
public:
void print() {
std::cout << "derived::print()\n";
}
void only_here() {
std::cout << "derived::only_here()\n";
}
void only_base2() {
std::cout << "Oops, here too!\n";
}
};
int main() {
base1* p1=new derived;
p1->print();
try {
derived* pD=boost::polymorphic_cast<derived*>(p1);
pD->only_here();
pD->only_base2();
base2* pB=boost::polymorphic_cast<base2*>(p1);
pB->only_base2();
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
delete p1;
}

To show how polymorphic_cast works, the first thing we did was to create an instance of derived and manipulate it through various pointers to the base and derived classes. The one function that will work out-of-the-box for p1 is print, which is a virtual function in base1 and derived. We then use a downcast to be able to call only_here, available only in derived:


derived* pD=boost::polymorphic_cast<derived*>(p1);
pD->only_here();

Note that if the polymorphic_cast fails, a std::bad_cast exception is thrown, so the code is protected by a try/catch block. This behavior is exactly the same as for dynamic_cast using reference types. The pointer pD is then used to call the function only_base2. The function is a non-virtual function in base2, but is also provided by derived, which hides the version in base2. Thus, we need to perform a crosscast to get a pointer to base2 to call base2::only_base2 rather than derived::only_base2.


base2* pB=boost::polymorphic_cast<base2*>(p1);
pB->only_base2();

Again, if the conversion fails, an exception is thrown. This example shows the ease with which error handling is performed when using polymorphic_cast if failed conversions are considered errors. There is no need to test for null pointers or to propagate an error out of the function explicitly. As we shall see shortly, dynamic_cast sometimes adds unnecessary complexity for this type of code; it may even lead to undefined behavior.

dynamic_cast Versus polymorphic_cast


To see the difference between these complementary casts,[3] let's put them head to head in a race against complexity. We'll reuse the classes base1, base2, and derived from the previous example. You'll note that the tests for a valid pointer when employing dynamic_cast on pointer types are both tedious and repetitious, which makes the tests unfortunate candidates for being omitted by stressed programmers.

[3] Technically, dynamic_cast is a cast operator, whereas polymorphic_cast is a function template.


void polymorphic_cast_example(base1* p) {
derived* pD=boost::polymorphic_cast<derived*>(p);
pD->print();
base2* pB=boost::polymorphic_cast<base2*>(p);
pB->only_base2();
}
void dynamic_cast_example(base1* p) {
derived* pD=dynamic_cast<derived*>(p);
if (!pD)
throw std::bad_cast();
pD->print();
base2* pB=dynamic_cast<base2*>(p);
if (!pB)
throw std::bad_cast();
pB->only_base2();
}
int main() {
base1* p=new derived;
try {
polymorphic_cast_example(p);
dynamic_cast_example(p);
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
delete p;
}

The two functions, polymorphic_cast_example and dynamic_cast_example, perform exactly the same work but in different ways. The difference is that wherever a dynamic_cast involving pointers is performed, we must remember to test the returned pointer to see if it is null. In our example, this designates an error, which should result in an exception of type bad_cast being thrown.[4] When using polymorphic_cast, the error handling is localized to the exception handler for std::bad_cast, which means that we do not need to worry about testing any returned values from casting between types. In this trivial example, it's not that hard to remember to test for validity of the returned pointer, but it still requires more work than when using polymorphic_cast. Add a couple of hundred lines of code, and two or three programmers performing maintenance in the function, and the risk of a forgotten test, or failure to throw the appropriate exception, increases drastically.

[4] Of course, the returned pointer must always be tested anyway, unless one is absolutely certain that the conversion will not fail.

polymorphic_cast Isn't Always the Right Choice


If a failed polymorphic pointer cast is not an error, you should use dynamic_cast rather than polymorphic_cast. For example, this is often the case when one uses dynamic_cast to test for certain types. Using exception handling to try conversions to several types makes for inefficient, hard-to-read code. It is this behavior of dynamic_cast that is its real strength. When using both polymorphic_cast and dynamic_cast, you can capture your intent very clearly. Even without polymorphic_cast, if people know about the different ways that dynamic_cast works, it is still possible to achieve the same level of safety, as is shown in the following example.


void failure_is_error(base1* p) {
try {
some_other_class& soc=dynamic_cast<some_other_class&>(*p);
// Use soc
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
}
void failure_is_ok(base1* p) {
if (some_other_class* psoc=
dynamic_cast<some_other_class*>(p)) {
// Use psoc
}
}

In this example, the pointer p is dereferenced[5] and the target type of the conversion is a reference to some_other_class. This invokes the throwing version of dynamic_cast. The second part of the example uses the non-throwing version by converting to a pointer type. Whether you see this as a clear and concise statement of the code's intent depends upon your experience. Veteran C++ programmers will understand the last example perfectly well. Will all of those reading the code be sufficiently familiar with the workings of dynamic_cast, or is it possible that they'll be unaware of the fact that it works differently depending on whether the type being converted is a pointer or reference? Will you or a maintenance programmer always remember to test for the null pointer? Will a maintenance programmer realize that dereferencing the pointer is necessary to get the exception if the conversion fails? Do you really want to write the same logic every time you need this behavior? Sorry for this rhetoricits intent is to make it painfully obvious that polymorphic_cast makes a stronger, clearer statement than dynamic_cast when a conversion failure should result in an exception. It either succeeds, producing a valid pointer, or it fails, throwing an exception. Simple rules are easier to remember.

[5] If the pointer p is null, the example results in undefined behavior because it will dereference a null pointer.

We haven't looked at how you can overload polymorphic_cast to account for unusual conversion needs, but it should be noted that it's possible. When would you want to change the default behavior of a polymorphic cast? One example is for handle/body-classes, where the rules for downcasting may be different from the default, or should be disallowed altogether.

Summary


It is imperative to remember that others need to maintain the code we write. That means that we have to make sure that the code and its intent are clear and understandable. In part, this can be accomplished by annotating the code, but it's much easier for everyone if the code is self-explanatory. polymorphic_cast documents the intent of code more clearly than dynamic_cast when an exception is expected for failed (pointer) conversions, and it makes for shorter code. If a failed conversion isn't considered an error, dynamic_cast should be used instead, which makes use of dynamic_cast clearer, too. Using dynamic_cast as the only means of expressing these different purposes is error prone and less clear. The difference between the throwing and non-throwing version is too subtle for many programmers.

When to use polymorphic_cast and dynamic_cast:

When a polymorphic cast failure is expected, use dynamic_cast<T*>. It makes clear that the failure is not an error.

When a polymorphic cast must succeed in order for the logic to be correct, use polymorphic_cast<T*>. It makes clear that a conversion failure is an error.

When performing polymorphic casts to reference types, use dynamic_cast.

/ 124