C++ Advanced Tutorial - Lesson 10
Related Blog Items
- C++ Tutorial Part 2 - Advanced
- C++ Basics - Tutorial
- C++ Advanced Tutorial - Lesson 11
- C++ Advanced Tutorial - Lesson 8
- C++ Advanced Tutorial - Lesson 3
Please refer Lesson 9..
10. ANSI/ISO C++ STANDARD LANGUAGE ADDITIONS
10.1 bool
10.2 Four Casts
10.3 Namespace
10.4 Run-Time Type Information (RTTI)
10.5 Operator Keywords
10.6 explicit Constructor
10.7 mutable Class Member
10. ANSI/ISO C++ STANDARD LANGUAGE ADDITIONS
10.1 bool
In C++ zero represents false and non-zero represents true. But bool type true and false is clearer and is preferred.
10.2 Four Casts
The ANSI/ISO C++ draft standard introduces four new cast operators to replace the old-style cast, which do all kinds of casting jobs. The new casts are less powerful and more specific, and each of them has separate purposes, thus give the program more precise control. Old casting is still legal, but new casts are preferable.
=>static_cast and dynamic_cast
static_cast operator and dynamic_cast is mainly used to cast up or dow the inheritance hierarchy - to cast base-class objects or pointers to derived-class objects or pointers, or vice versa. Consider the following example:
class Base { public: Method1() {}; }; class Derived : public Base { public: Method2() {}; }; void DoSomething(Base * pBase) { Derived * pDerived1 = static_cast<Derived *>(pBase); pDerived1->Method2(); Derived * pDerived2 = dynamic_cast<Derived *>(pBase); pDerived2->Method2(); }
static_cast only perform type checking at compile time. So it is safer than casting with “( )”. But it does not perform run-time type checking, so it is the programmer’s responsibility to make sure that pBase is pointing to a Derived object. If not, there will be a run time error.
In contrast, dynamic_cast performs RTTI, and if pBase is only pointing to Base object, dynamic_cast will find out and return 0. Therefore, you can decide the whether the cast is successful by checking this:
if(dynamic_cast
=>const_cast
const_cast casts away const or volatile. It only works on pointers, not objects. You can use it to modify a data member in a const method:
void Test::print() const // Test is a class { const_cast<Test *>(this)->member1++; // member1 is a data member of Test cout << member1 << endl; }
Such a capability can solve the problem for an intelligent pointer class:
#ifndef POINTER_H #define POINTER_H template <class Type> class Pointer { public: Pointer() : ptr(0), owner(false) {} Pointer(Type * p) :ptr(p), owner(true) {} Pointer(const Pointer & obj) { ptr = obj. ptr; if(obj. owner == true) { owner = true; const_cast< Pointer * >(&obj)->owner = false; } else owner = false; } const Pointer & operator=(const Pointer & obj) { ptr = obj. ptr; if(obj. owner == true) { owner = true; const_cast< Pointer * >(&obj)->owner = false; } else owner = false; return *this; } ~ Pointer() { if(owner == true) delete ptr; } int operator==(const Pointer &other) const { return ptr == other. ptr; } Type * pointer() const { return ptr; } Type * operator->() const { return ptr; } Type & operator*() const { return *ptr; } private: Type * ptr; bool owner; }; #endif
Such a pointer object is fully intelligent: it allows shallow copy - multiple pointer objects pointing to the same server object - to avoid the copy of pointed objects which is most probably unnecessary and time consuming. It knows to delete the server object to which it is pointing, and meanwhile avoiding multiple deleting on the same object.
The key point of this class is an extra data member “owner”, representing whether a pointer object is the owner of its server object. There may be multiple pointer objects pointing to the same server object, but among them there is only one owner, and only the owner will delete the server object.
To accomplish this, in its copy constructor and assignment operator, it transfers the ownership from “right” object to “this” object, setting the right object’s owner to be false. Considering that the right object in these two methods must be constant (otherwise it can not be applied on constant objects), we need const_cast to cast away the const of the right object before we modify its data member “owner”.
=>reinterpret_cast
reinterpret_cast is used for cases that yeilds implementation-dependent results, such as casing between function pointer types.
Because it allows you to do nonstandard strange conversions, dangerous manipulations can be done, and the result may be machine-dependent.
10.3 Namespace
Each namespace defines a scope where global identifiers and variables are placed:
namespace Test1 { const double PI = 3.14159; const double E = 2.71828 void print(); namespace Inner { enum Day {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}; } } void Test1::print() { cout << PI << Inner::Monday; }
Notice that unlike classes, there is no semicolon after the definition.
Namespace can be nested.
The definition of a namespace must either occupy the scope or be nested within other namespace.
Because method “print” is a member of namespace Test1, it can directly access other data members of Test1, and access nested namespace Inner through its name.
To use a namespace member, the member’s name must be qualified with the namespace name and binary scope resolution operator:
double area = Test1::PI * 2.0 * Radius;
if(day == Test1::Inner::Tuesday)
In case a namespace’s members are frequently used in a file, to simplify the invoking process, place statement
using namespace Test1;
then in the following part of the file the namespace members do not need to be proceeded with “Test::”. It can also be used to directly access only one member of the namespace:
using namespace Test1::PI;
using namespace std;
informs the compiler that namespace std is being used. The contents of header file
Unnamed namespace members occupy the global namespace, are directly accessible and do not have to be qualified with a namespace name. Global variables are actually part of global namespace.
Ideally, in large programs, every entity should be declared in a class, method, block, or namespace. This helps clarify every entity’s role.
Classes do provide us with named scopes, but namespaces allow us to manage things more precisely. Before namespaces were introduced we had to create classes just for the purpose of defining a named scope, and we had to use typedef to get some of the control namespaces give us.
10.4 Run-Time Type Information (RTTI)
Because of polymorphism, when you write your program, you may not be able to know the exact type you are dealing with. But there are cases in which you do need to find out the exact type of the polymorphic object and deal with it differently according to its type. Run-time type information (RTTI) is used to find out the type of a polymorphic object.
Header file
class Base { ... } class Derived : public Base { ... } int main() { Base1 * pBase1 = new Derived; const type_info & t1 = typeid(pBase1); const type_info & t2 = typeid(*pBase1); cout << "typeid(pBase1) = " << t1.name() << endl; cout << "typeid(*pBase1) = " << t2.name() << endl; }
If class Base and Derived are polymorphic types i.e. they have virtual functions, the output will be:
typeid(pBase1) = class Base1 * typeid(*pBase1) = class Derived
If class Base and Derived do not have virtual functions, the output will be:
typeid(pBase1) = class Base1 * typeid(*pBase1) = class Base
10.5 Operator Keywords
The ANSI/ISO standard provides operator keywords that can be used in place of some operators, in case that the keyboard you are using does not have these keys. For example, “and” can be used for “&&”, “bitand” for “&”, etc.
10.6 explicit Constructor
When a method is expecting an object which has a one-argument constructor but you pass the argument, compiler will implicitly call that one-argument constructor and convert that passed argument to the object:
class Base { public: Base(int a) : member(a) { cout << "Base constructor called with " << a << endl; } int member; }; void test(Base obj1) { cout << "Base object's member = " << obj1.member; } int main() { test(333); }
The output will be:
Base constructor called with 333
Base object’s member = 333
In same cases such an automatic implicit conversion may cause trouble. You can use keyword explicit in front of the constructor to suppress the implicit conversion. If you want to convert, you have to use explicit conversion such as static_cast, without which compiler will reject the conversion and prompt error message.
class Base { public: explicit Base(int a) : member(a) { cout << "Base constructor called with " << a << endl; } int member; }; void test(Base obj1) { cout << "Base object's member = " << obj1.member; } int main() { test( static_cast<Base>(333) ); }
10.7 mutable Class Member
C++ provides mutable class member as an alternative to const_cast. A mutable data member is always modifiable even in a const method of a const object. The difference between const_cast and mutable is: for a non-mutable data member in a const method, every time you modify it, you have to use const_cast. This actually reduces the chance of accidental modification.
Popularity: 18%
You need to log on to convert this article into PDF
Related Blog Items - C++ Tutorial Part 2 - Advanced
- C++ Basics - Tutorial
- C++ Advanced Tutorial - Lesson 11
- C++ Advanced Tutorial - Lesson 8
- C++ Advanced Tutorial - Lesson 3
Related Blog Items
- C++ Tutorial Part 2 - Advanced
- C++ Basics - Tutorial
- C++ Advanced Tutorial - Lesson 11
- C++ Advanced Tutorial - Lesson 8
- C++ Advanced Tutorial - Lesson 3
No Comments
No comments yet.