Implicit conversion
Implicit conversions are automatically performed when a value is copied to a compatible type. For example:
- short a=2000;
- int b;
- b=a;
Converting to int from some smaller integer type, or to double from float is known as promotion, and is guaranteed to produce the exact same value in the destination type. Other conversions between arithmetic types may not always be able to represent the same value exactly:
- If a negative integer value is converted to an unsigned type
- The conversions from/to bool
- If the conversion is from a floating-point type to an integer type
- Otherwise, if the conversion is between numeric types of the same kind
Some of these conversions may imply a loss of precision, which the compiler can signal with a warning. This warning can be avoided with an explicit conversion. For non-fundamental types, arrays and functions implicitly convert to pointers, and pointers in general allow the following conversions:
Implicit conversions with classes
In the world of classes, implicit conversions can be controlled by means of three member functions:
For example:
- // implicit conversion of classes:
- #include
- using namespace std;
- class A {
- public:
- A(){str.assign("A");}
- A(const char* cont){str.assign(cont);}
- friend ostream &operator<<(ostream &s, A a);
- private:
- string str;
- };
- class B {
- public:
- // conversion from A (constructor):
- B (const A& x) {str.assign("conversion from A (constructor)");}
- // conversion from A (assignment):
- B& operator= (const A& x)
- {
- this->str.assign("conversion from A (assignment)");
- return *this;
- }
- // conversion to A (type-cast operator)
- operator A() {return A("conversion to A (type-cast operator)");}
- friend ostream &operator<<(ostream &s, B b);
- private:
- string str;
- };
- ostream &operator<<(ostream &s, A a) {
- s << a.str;
- return s;
- }
- ostream &operator<<(ostream &s, B b) {
- s << b.str;
- return s;
- }
- int main ()
- {
- A foo;
- B bar = foo; // calls constructor
- cout << bar << endl;
- bar = foo; // calls assignment
- cout << bar << endl;
- foo = bar; // calls type-cast operator
- cout << foo << endl;
- return 0;
- }
Keyword explicit
On a function call, C++ allows one implicit conversion to happen for each argument. This may be somewhat problematic for classes, because it is not always what is intended. For example, if we add the following function to the last example:
- void fn (B arg) {}
- fn (foo);
- // explicit:
- #include
- using namespace std;
- class A {};
- class B {
- public:
- explicit B (const A& x) {}
- B& operator= (const A& x) {return *this;}
- operator A() {return A();}
- };
- void fn (B x) {}
- int main ()
- {
- A foo;
- B bar (foo);
- bar = foo;
- foo = bar;
- // fn (foo); // not allowed for explicit ctor.
- fn (bar);
- return 0;
- }
- B bar = foo;
Type casting
C++ is a strong-typed language. Many conversions, specially those that imply a different interpretation of the value, require an explicit conversion, known in C++ as type-casting. There exist two main syntaxes for generic type-casting: functional and c-like:
- double x = 10.3;
- int y;
- y = int (x); // functional notation
- y = (int) x; // c-like cast notation
- // class type-casting
- #include
- using namespace std;
- class Dummy {
- double i,j;
- };
- class Addition {
- int x,y;
- public:
- Addition (int a, int b) { x=a; y=b; }
- int result() { return x+y;}
- };
- int main () {
- Dummy d;
- Addition * padd;
- padd = (Addition*) &d;
- cout << padd->result();
- return 0;
- }
- padd = (Addition*) &d;
In order to control these types of conversions between classes, we have four specific casting operators: dynamic_cast, reinterpret_cast, static_cast and const_cast. Their format is to follow the new type enclosed between angle-brackets (<>) and immediately after, the expression to be converted between parentheses.
- dynamic_cast
(expression) - reinterpret_cast
(expression) - static_cast
(expression) - const_cast
(expression)
but each one with its own special characteristics!
dynamic_cast
dynamic_cast can only be used with pointers and references to classes (or with void*). Its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type.
This naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion. But dynamic_cast can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members) if -and only if- the pointed object is a valid complete object of the target type. For example:
- // dynamic_cast
- #include
- #include
- using namespace std;
- class Base { virtual void dummy() {} };
- class Derived: public Base { int a; };
- int main () {
- try {
- Base * pba = new Derived;
- Base * pbb = new Base;
- Derived * pd;
- pd = dynamic_cast
(pba); - if (pd==0) cout << "Null pointer on first type-cast.\n";
- pd = dynamic_cast
(pbb); - if (pd==0) cout << "Null pointer on second type-cast.\n";
- } catch (exception& e) {cout << "Exception: " << e.what();}
- return 0;
- }
Compatibility note:
The code above tries to perform two dynamic casts from pointer objects of type Base* (pba and pbb) to a pointer object of type Derived*, but only the first one is successful. Notice their respective initializations:
- Base * pba = new Derived;
- Base * pbb = new Base;
When dynamic_cast cannot cast a pointer because it is not a complete object of the required class -as in the second conversion in the previous example- it returns a null pointer to indicate the failure. If dynamic_cast is used to convert to a reference type and the conversion is not possible, an exception of type bad_cast is thrown instead.
dynamic_cast can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes), and casting any pointer of any type to a void* pointer.
static_cast
static_cast can perform conversions between pointers to related classes, not only upcasts (from pointer-to-derived to pointer-to-base), but also downcasts (from pointer-to-base to pointer-to-derived). No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. Therefore, it is up to the programmer to ensure that the conversion is safe. On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.
- class Base {};
- class Derived: public Base {};
- Base * a = new Base;
- Derived * b = static_cast
(a);
static_cast is also able to perform all conversions allowed implicitly (not only those with pointers to classes), and is also able to perform the opposite of these. It can:
Additionally, static_cast can also perform the following:
Explicitly call a single-argument constructor or a conversion operator.
reinterpret_cast
reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes. The operation result is a simple binary copy of the value from one pointer to the other. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
It can also cast pointers to or from integer types. The format in which this integer value represents a pointer is platform-specific. The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t), is guaranteed to be able to be cast back to a valid pointer.
The conversions that can be performed by reinterpret_cast but not by static_cast are low-level operations based on reinterpreting the binary representations of the types, which on most cases results in code which is system-specific, and thus non-portable. For example:
- class A { /* ... */ };
- class B { /* ... */ };
- A * a = new A;
- B * b = reinterpret_cast(a);
const_cast
This type of casting manipulates the constness of the object pointed by a pointer, either to be set or to be removed. For example, in order to pass a const pointer to a function that expects a non-const argument:
- // const_cast
- #include
- using namespace std;
- void print (char * str)
- {
- cout << str << '\n';
- }
- int main () {
- const char * c = "sample text";
- print ( const_cast<char *> (c) );
- return 0;
- }
typeid
typeid allows to check the type of an expression:
- typeid (expression)
- // typeid
- #include
- #include
- using namespace std;
- int main () {
- int * a,b;
- a=0; b=0;
- if (typeid(a) != typeid(b))
- {
- cout << "a and b are of different types:\n";
- cout << "a is: " << typeid(a).name() << '\n';
- cout << "b is: " << typeid(b).name() << '\n';
- }
- return 0;
- }
When typeid is applied to classes, typeid uses the RTTI to keep track of the type of dynamic objects. When typeid is applied to an expression whose type is a polymorphic class, the result is the type of the most derived complete object:
- // typeid, polymorphic class
- #include
- #include
- #include
- using namespace std;
- class Base { virtual void f(){} };
- class Derived : public Base {};
- int main () {
- try {
- Base* a = new Base;
- Base* b = new Derived;
- cout << "a is: " << typeid(a).name() << '\n';
- cout << "b is: " << typeid(b).name() << '\n';
- cout << "*a is: " << typeid(*a).name() << '\n';
- cout << "*b is: " << typeid(*b).name() << '\n';
- } catch (exception& e) { cout << "Exception: " << e.what() << '\n'; }
- return 0;
- }
Notice how the type that typeid considers for pointers is the pointer type itself (both a and b are of type class Base *). However, when typeid is applied to objects (like *a and *b) typeid yields their dynamic type (i.e. the type of their most derived complete object).
If the type typeid evaluates is a pointer preceded by the dereference operator (*), and this pointer has a null value, typeid throws a bad_typeid exception.
Supplement:
* 物件導向 : 多型 - 執行時期型態資訊(RTTI)
沒有留言:
張貼留言