Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 91: Explicit type conversions

An expression can be explicitly converted or cast to type T using dynamic_cast<T>, static_cast<T>, reinterpret_cast<T>, or const_cast<T>, depending on what type of cast is intended.

C++ also supports function-style cast notation, T(expr), and C-style cast notation, (T)expr.

Section 91.1: C-style casting

C-Style casting can be considered 'Best e ort' casting and is named so as it is the only cast which could be used in C. The syntax for this cast is (NewType)variable.

Whenever this cast is used, it uses one of the following c++ casts (in order):

const_cast<NewType>(variable)

static_cast<NewType>(variable)

const_cast<NewType>(static_cast<const NewType>(variable))

reinterpret_cast<const NewType>(variable)

const_cast<NewType>(reinterpret_cast<const NewType>(variable))

Functional casting is very similar, though as a few restrictions as the result of its syntax: NewType(expression). As a result, only types without spaces can be cast to.

It's better to use new c++ cast, because s more readable and can be spotted easily anywhere inside a C++ source code and errors will be detected in compile-time, instead in run-time.

As this cast can result in unintended reinterpret_cast, it is often considered dangerous.

Section 91.2: Casting away constness

A pointer to a const object can be converted to a pointer to non-const object using the const_cast keyword. Here we use const_cast to call a function that is not const-correct. It only accepts a non-const char* argument even though it never writes through the pointer:

void bad_strlen(char*);

 

const char* s = "hello, world!";

 

bad_strlen(s);

// compile error

bad_strlen(const_cast<char*>(s)); // OK, but it's better to make bad_strlen accept const char*

const_cast to reference type can be used to convert a const-qualified lvalue into a non-const-qualified value.

const_cast is dangerous because it makes it impossible for the C++ type system to prevent you from trying to modify a const object. Doing so results in undefined behavior.

const int x = 123;

int& mutable_x = const_cast<int&>(x);

mutable_x = 456; // may compile, but produces *undefined behavior*

Section 91.3: Base to derived conversion

A pointer to base class can be converted to a pointer to derived class using static_cast. static_cast does not do any run-time checking and can lead to undefined behaviour when the pointer does not actually point to the desired type.

GoalKicker.com – C++ Notes for Professionals

482

struct Base

{};

 

struct Derived : Base {};

 

Derived d;

 

 

Base* p1

= &d;

 

Derived*

p2

= p1;

// error; cast required

Derived* p3 = static_cast<Derived*>(p1); // OK; p2 now points to Derived object Base b;

Base* p4 = &b;

Derived* p5 = static_cast<Derived*>(p4); // undefined behaviour since p4 does not // point to a Derived object

Likewise, a reference to base class can be converted to a reference to derived class using static_cast.

struct Base {};

 

struct Derived : Base {};

 

Derived d;

 

 

 

Base& r1 = d;

 

 

Derived& r2

=

r1;

// error; cast required

Derived& r3

=

static_cast<Derived&>(r1); // OK; r3 now refers to Derived object

If the source type is polymorphic, dynamic_cast can be used to perform a base to derived conversion. It performs a run-time check and failure is recoverable instead of producing undefined behaviour. In the pointer case, a null pointer is returned upon failure. In the reference case, an exception is thrown upon failure of type std::bad_cast (or a class derived from std::bad_cast).

struct Base { virtual ~Base(); }; // Base is polymorphic struct Derived : Base {};

Base* b1 = new Derived;

Derived* d1 = dynamic_cast<Derived*>(b1); // OK; d1 points to Derived object Base* b2 = new Base;

Derived* d2 = dynamic_cast<Derived*>(b2); // d2 is a null pointer

Section 91.4: Conversion between pointer and integer

An object pointer (including void*) or function pointer can be converted to an integer type using reinterpret_cast. This will only compile if the destination type is long enough. The result is implementationdefined and typically yields the numeric address of the byte in memory that the pointer pointers to.

Typically, long or unsigned long is long enough to hold any pointer value, but this is not guaranteed by the standard.

Version ≥ C++11

If the types std::intptr_t and std::uintptr_t exist, they are guaranteed to be long enough to hold a void* (and hence any pointer to object type). However, they are not guaranteed to be long enough to hold a function pointer.

Similarly, reinterpret_cast can be used to convert an integer type into a pointer type. Again the result is implementation-defined, but a pointer value is guaranteed to be unchanged by a round trip through an integer type. The standard does not guarantee that the value zero is converted to a null pointer.

void register_callback(void (*fp)(void*), void* arg); // probably a C API void my_callback(void* x) {

std::cout << "the value is: " << reinterpret_cast<long>(x); // will probably compile

}

long x; std::cin >> x;

GoalKicker.com – C++ Notes for Professionals

483

register_callback(my_callback,

reinterpret_cast<void*>(x)); // hopefully this doesn't lose information...

Section 91.5: Conversion by explicit constructor or explicit conversion function

A conversion that involves calling an explicit constructor or conversion function can't be done implicitly. We can request that the conversion be done explicitly using static_cast. The meaning is the same as that of a direct initialization, except that the result is a temporary.

class C { std::unique_ptr<int> p;

public:

explicit C(int* p) : p(p) {}

};

 

 

void f(C c);

 

 

void g(int* p) {

 

 

f(p);

//

error: C::C(int*) is explicit

f(static_cast<C>(p)); //

ok

f(C(p));

//

equivalent to previous line

C c(p); f(c);

//

error: C is not copyable

}

 

 

Section 91.6: Implicit conversion

static_cast can perform any implicit conversion. This use of static_cast can occasionally be useful, such as in the following examples:

When passing arguments to an ellipsis, the "expected" argument type is not statically known, so no implicit conversion will occur.

const double x = 3.14;

printf("%d\n", static_cast<int>(x)); // prints 3

//printf("%d\n", x); // undefined behaviour; printf is expecting an int here

//alternative:

//const int y = x; printf("%d\n", y);

Without the explicit type conversion, a double object would be passed to the ellipsis, and undefined behaviour would occur.

A derived class assignment operator can call a base class assignment operator like so:

struct Base { /* ... */ }; struct Derived : Base {

Derived& operator=(const Derived& other) { static_cast<Base&>(*this) = other;

//alternative:

//Base& this_base_ref = *this; this_base_ref = other;

}

};

Section 91.7: Enum conversions

static_cast can convert from an integer or floating point type to an enumeration type (whether scoped or

GoalKicker.com – C++ Notes for Professionals

484

unscoped), and vice versa. It can also convert between enumeration types.

The conversion from an unscoped enumeration type to an arithmetic type is an implicit conversion; it is possible, but not necessary, to use static_cast.

Version ≥ C++11

When a scoped enumeration type is converted to an arithmetic type:

If the enum's value can be represented exactly in the destination type, the result is that value.

Otherwise, if the destination type is an integer type, the result is unspecified.

Otherwise, if the destination type is a floating point type, the result is the same as that of converting to the underlying type and then to the floating point type.

Example:

enum class

Format {

 

TEXT =

0,

 

PDF = 1000,

 

OTHER = 2000,

 

};

 

 

Format f = Format::PDF;

 

int a = f;

 

// error

int b = static_cast<int>(f);

// ok; b is 1000

char c = static_cast<char>(f);

// unspecified, if 1000 doesn't fit into char

double d =

static_cast<double>(f); // d is 1000.0... probably

 

 

 

When an integer or enumeration type is converted to an enumeration type:

If the original value is within the destination enum's range, the result is that value. Note that this value might be unequal to all enumerators.

Otherwise, the result is unspecified (<= C++14) or undefined (>= C++17).

Example:

enum Scale

{

 

SINGLE

= 1,

 

DOUBLE

= 2,

 

QUAD =

4

 

};

 

 

 

Scale s1 =

1;

// error

Scale s2

=

static_cast<Scale>(2); // s2 is DOUBLE

Scale s3

=

static_cast<Scale>(3); // s3 has value 3, and is not equal to any enumerator

Scale s9

=

static_cast<Scale>(9); // unspecified value in C++14; UB in C++17

 

 

 

 

Version ≥ C++11

When a floating point type is converted to an enumeration type, the result is the same as converting to the enum's underlying type and then to the enum type.

enum Direction { UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3,

};

GoalKicker.com – C++ Notes for Professionals

485