1. Trang chủ
  2. » Công Nghệ Thông Tin

HandBooks Professional Java-C-Scrip-SQL part 48 doc

14 65 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 14
Dung lượng 57,77 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Whereas dynamic_cast tests the downcasts and returns the null pointer or throws an exception on failure, static_cast just performs the necessary pointer arithmetic and leaves it up to th

Trang 1

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

Trang 2

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

polymorphic_downcast

Header: "boost/cast.hpp"

Sometimes dynamic_cast is considered too inefficient (measured, I'm sure!) There is runtime overhead for performing dynamic_casts To avoid that

overhead, it is tempting to use static_cast, which doesn't have such

performance implications static_cast for downcasts can be dangerous and

Trang 3

cause errors, but it is faster than dynamic_cast If the extra speed is required,

we must make sure that the downcasts are safe Whereas dynamic_cast tests the downcasts and returns the null pointer or throws an exception on failure,

static_cast just performs the necessary pointer arithmetic and leaves it up to the programmer to make sure that the conversion is valid To be sure that

static_cast is safe for downcasting, you must make sure to test every

conversion that will be performed polymorphic_downcast tests the cast with dynamic_cast, but only in debug builds; it then uses static_cast to

perform the conversion In release mode, only the static_cast is performed The nature of the cast implies that you know it can't possibly fail, so there is no error handling, and no exception is ever thrown So what happens if a

polymorphic_downcast fails in a non-debug build? Undefined behavior Your computer may melt The Earth may stop spinning You may float above the clouds The only thing you can safely assume is that bad things will happen to your program If a polymorphic_downcast fails in a debug build, it asserts on the null pointer result of dynamic_cast

Before considering how to speed up a program by exchanging

polymorphic_downcast for dynamic_cast, review the design

Optimizations on casts are likely indicators of a design problem If the downcasts are indeed needed and proven to be performance bottlenecks,

polymorphic_downcast is what you need You can only find erroneous casts

in testing, not production (release builds), and if you've ever had to listen to a

screaming customer on the other end of the phone, you know that catching errors

in testing is rather important and makes life a lot easier Even more likely is that you've been the customer from time to time, and know firsthand how annoying it is

to find and report someone else's problems So, use polymorphic_downcast

if needed, but tread carefully

Usage

polymorphic_downcast is used in situations where you'd normally use

dynamic_cast but don't because you're sure which conversions will take place, that they will all succeed, and that you need the improved performance it brings Nota bene: Be sure to test all possible combinations of types and casts using

polymorphic_downcast If that's not possible, do not use

polymorphic_downcast; use dynamic_cast instead When you decide to

go ahead and use polymorphic_downcast, include "boost/cast.hpp"

Trang 4

#include <iostream>

#include "boost/cast.hpp"

struct base {

virtual ~base() {};

};

struct derived1 : public base {

void foo() {

std::cout << "derived1::foo()\n";

}

};

struct derived2 : public base {

void foo() {

std::cout << "derived2::foo()\n";

}

};

void older(base* p) {

// Logic that suggests that p points to derived1 omitted

derived1* pd=static_cast<derived1*>(p);

pd->foo(); // < What will happen here?

}

void newer(base* p) {

// Logic that suggests that p points to derived1 omitted

derived1* pd=boost::polymorphic_downcast<derived1*>(p);

// ^ The above cast will cause an assertion in debug builds

pd->foo();

}

int main() {

derived2* p=new derived2;

older(p); // < Undefined

newer(p); // < Well defined in debug build

}

The static_cast in the function older will succeed,[6] and as bad luck would have it, the existence of a member function foo lets the error (probably, but again,

no guarantees hold here) slip until someone with an error report in one hand and a debugger in the other starts looking into some strange behavior When the pointer

is downcast using static_cast to a derived1*, the compiler has no option but to trust the programmer that the conversion is valid However, the pointer passed to older is in fact pointing to an instance of derived2 Thus, the pointer

pd in older actually points to a completely different type, which means that

Trang 5

anything can happen That's the risk one takes when using a static_cast to downcast The conversion will always "succeed" but the pointer may not be valid [6]

At least it will compile

In the call to function newer, the "better static_cast,"

polymorphic_downcast not only catches the error, it is also kind enough to pinpoint the location of the error by asserting Of course, that's true only for debug builds, where the cast is tested by a dynamic_cast Letting an invalid

conversion through to release will cause grief In other words, you get added safety for debug builds, but that doesn't necessarily mean that you've tried all possible conversions

Summary

Performing downcasts using static_cast is dangerous in many situations You should almost never do it, but if the need does arise, some additional safety can be bought by using polymorphic_downcast It adds tests in debug builds, which can help find conversion errors, but you must test all possible conversions to make its use safe

 If you are downcasting and need the speed of static_cast in release builds, use polymorphic_downcast; at least you'll get assertions for errors during testing

 If it's not possible to cover all possible casts in testing, do not use

polymorphic_downcast

Remember that this is an optimization, and you should only apply optimizations after profiling demonstrates the need for them

numeric_cast

Header: "boost/cast.hpp"

Conversions between integral types can often produce unexpected results For example, a long can typically hold a much greater range of values than a short,

Trang 6

so what happens when assigning a long to a short and the long's value is outside of short's range? The answer is that the result is implementation defined (a nice term for "you can never know for sure") Signed to unsigned conversions between same size integers are fine, so long as the signed value is positive, but what happens if the signed value is negative? It turns into a large unsigned value, which is indeed a problem if that was not the intention numeric_cast helps ensure valid conversions by testing whether the range is preserved and by throwing

an exception if it isn't

7 The C++ Standard covers promotions and conversions for numeric types in

§4.5-4.9

Before we can fully appreciate numeric_cast, we must understand the rules that govern conversions and promotions of integral types The rules are many and sometimes subtlethey can trap even the experienced programmer Rather than stating all of the rules7 and then carry on, I'll give you examples of conversions that are subject to undefined or surprising behavior, and explain which rules the

conversions adhere to

When assigning to a variable from one of a different numeric type, a conversion occurs This is perfectly safe when the destination type can hold any value that the source can, but is unsafe otherwise For example, a char generally cannot hold the maximum value of an int, so when an assignment from int to char occurs, there is a good chance that the int value cannot be represented in the char

When the types differ in the range of values they can represent, we must make sure that the actual value to convert is in the valid range of the destination type

Otherwise, we enter the land of implementation-defined behavior; that's what

happens when a value outside of the range of possible values is assigned to a

numeric type.[8] Implementation-defined behavior means that the implementation is free to do whatever it wants to; different systems may well have totally different behavior numeric_cast can ensure that the conversions are valid and legal or they will not be allowed

[8]

Unsigned arithmetic notwithstanding; it is well defined for these cases

Usage

numeric_cast is a function template that looks like a C++ cast operator and is parameterized on both the destination and source types The source type can be implicitly deduced from the function argument To use numeric_cast, include

Trang 7

the header "boost/cast.hpp" The following two conversions use

numeric_cast to safely convert an int to a char, and a double to a

float

char c=boost::numeric_cast<char>(12);

float f=boost::numeric_cast<float>(3.001);

One of the most common numeric conversion problems is assigning a value from a type with a wider range than the one being assigned to Let's see how numeric_cast

can help

Assignment from a Larger to a Smaller Type

When assigning a value from a larger type (for example, long) to a smaller type (for example, short), there is a chance that the value is too large or too small to

be represented in the destination type If this happens, the result is (yes, you've guessed it) implementation-defined We'll talk about the potential problems with unsigned types later; let's just start with the signed types There are four built-in signed integral types in C++:

 signed char

 short int (short)

 long int (long)

There's not much one can say with absolute certainty about which type is larger[9] than others, but typically, the listing is in increasing size, with the exception that int and long often hold the same range of values They're all distinct types, though, even if they're the same size To see the sizes on your system, use either sizeof(T) or std::numeric_limits<T>::max() and

std::numeric_limits<T>::min()

[9]

Of course, the ranges of signed and unsigned types are different even if the types have the same size

When assigning one signed integral type to another, the C++ Standard says:

"If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bitfield width); otherwise, the value is

Trang 8

[10]

See §4.7.3 of the C++ Standard

The following piece of code gives an example of how these

implementation-defined values are often the result of seemingly innocent assignments, and finally how they are avoided with the help of numeric_cast

#include <iostream>

#include "boost/cast.hpp"

#include "boost/limits.hpp"

int main() {

std::cout << "larger_to_smaller example\n";

// Conversions without numeric_cast

long l=std::numeric_limits<short>::max();

short s=l;

std::cout << "s is: " << s << '\n';

s=++l;

std::cout << "s is: " << s << "\n\n";

// Conversions with numeric_cast

try {

l=std::numeric_limits<short>::max();

s=boost::numeric_cast<short>(l);

std::cout << "s is: " << s << '\n';

s=boost::numeric_cast<short>(++l);

std::cout << "s is: " << s << '\n';

}

catch(boost::bad_numeric_cast& e) {

std::cout << e.what() << '\n';

}

}

Utilizing std::numeric_limits, the long l is initialized to the maximum value that a short can possibly hold That value is assigned to the short s and printed After that, l is incremented by one, which means that it now holds a value that cannot be represented by a short; it is outside the range of values that a short can represent After assigning from the new value of l to s, s is printed again What's the value, you might ask? Well, because the assignment results in implementation-defined behavior, that depends upon the platform On my system, with my compiler, it turns out that the result is a large negative value, which

Trang 9

implies that the value has been wrapped There's no telling[11] what it will be on your system without running the preceding code Next, the same operations are performed again, but this time using numeric_cast The first cast succeeds, because the value is within range The second, however, fails, and the result is that

an exception of type bad_numeric_cast is thrown The output of the program

is as follows

[11]

Although the behavior and value demonstrated here is very common on 32-bit platforms

larger_to_smaller example

s is: 32767

s is: -32768

s is: 32767

bad numeric cast: loss of range in numeric_cast

A benefit that might be even more important than dodging the implementation-defined value is that numeric_cast helps us avoid errors that are otherwise very hard to trap The strange value could be passed on to other parts of the

application, perhaps working in some cases, but almost certainly yielding the

wrong result Of course, this only happens for certain values, and if those values seldom occur, the error will be very hard to track down Such errors are insidious because they happen only for some values rather than all of the time

Loss of precision or range is not unusual, and if you aren't absolutely certain that a value too large or too small for the destination type will never be assigned,

numeric_cast is the tool for you You can even use numeric_cast when it's unnecessary; the maintenance programmer may not have the same insight as you

do Note that although we have covered only signed types here, the same principles apply to unsigned integral types, too

Special CaseUnsigned Integral Type As Destination

Unsigned integral types have a very interesting propertyany numeric value can be legally assigned to them! There is no notion of positive or negative overflow when

it comes to unsigned types They are reduced modulo the number that is one

greater than the largest value of the destination type Say what? An example in code might make it clearer

#include <iostream>

Trang 10

#include "boost/limits.hpp"

int main() {

unsigned char c;

long l=std::numeric_limits<unsigned char>::max()+14;

c=l;

std::cout << "c is: " << (int)c << '\n';

long reduced=l%(std::numeric_limits<unsigned char>::max()+1);

std::cout << "reduced is: " << reduced << '\n';

}

The output of running the program follows:

c is: 13

reduced is: 13

The example assigns a value that is certainly greater than what an unsigned char can hold, and then that same value is calculated The workings of the

assignment is shown in this line:

long reduced=l%(std::numeric_limits<unsigned char>::max()+1);

This behavior is often referred to as value wrapping If you want to use this

property of unsigned integral types, there is no need to use numeric_cast in those situations Furthermore, numeric_cast won't accept it

numeric_cast's intent is to catch errors, and this is considered an error because

it is the result of a typical user misunderstanding If the destination type cannot represent the value that is being assigned, a bad_numeric_cast exception is thrown Just because unsigned integer arithmetic is well defined doesn't make the programmer's error less fatal.[12] For numeric_cast, the important aspect is to preserve the actual value

[12]

The point: If you really want value wrapping, don't use numeric_cast

Mixing Signed and Unsigned Integral Types

It's easy to have fun[13] when mixing signed and unsigned types, especially when performing arithmetic operations Plain assignments offer some clever pitfalls, too The most common problem is assigning a negative value to an unsigned type The result is almost certainly not what was intended Another issue is when assigning from an unsigned type to a signed type of the same size Somehow, it seems to be

Ngày đăng: 06/07/2014, 03:20

TỪ KHÓA LIÊN QUAN