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

thinking in c 2nd ed volume 2 rev 20 - phần 8 pps

52 260 0

Đ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 52
Dung lượng 124,46 KB

Nội dung

365 z 516 57. Create a valarray<int> with 12 random values. Create another valarray<int> with 20 random values. You will interpret the first valarray as a 3 x 4 matrix of ints and the second as a 4 x 5 matrix of ints, and multiply them by the rules of matrix multiplication. Store the result in a valarray<int> of size 15, representing the 3 x 5 result matrix. Use slices to multiply the rows of the first matrix time the columns of the second. Print the result in rectangular matrix form. Special Topics The mark of a professional in any field appears in his or her attention to the finer points of the craft. In this part of the book we discuss advanced features of C++ along with development techniques used by polished C++ professionals. Once in a great while you may need to depart from the conventional wisdom of sound object- oriented design by inspecting the runtime type of an object for special processing. Most of the time you should let virtual functions do that job for you, but when writing special-purpose software tools, such as debuggers, database viewers, or class browsers, you’ll need to determine type information at runtime. This is where the runtime type identification (RTTI) mechanism comes into play, which is the topic of Chapter 8. Multiple inheritance has taken a bad rap over the years, and some languages don’t even support it. Nonetheless, when used properly, it can be a powerful tool for crafting elegant, efficient code. A number of standard practices involving multiple inheritance have evolved over the years, which we present in Chapter 9. Perhaps the most notable innovation in software development since object-oriented techniques is the use of design patterns. A design pattern describes and presents solutions for many of the common problems involved in designing software, and can be applied in many situations and implemented in any language. In chapter 10 we describe a selected number of widely-used design patterns and implement them in C++. Chapter 11 explains in detail the benefits and challenges of multi-threaded programming. The current version of standard C++ does not specify support for threads, even though most operating systems support them. We use a portable, freely-available thread library to illustrate how C++ programmers can take advantage of threads to build more usable and responsive applications. 8: Runtime type identification Runtime type identification (RTTI) lets you find the dynamic type of an object when you have only a pointer or a reference to the base type. This can be thought of as a “secondary” feature in C++, pragmatism to help out when you get into rare messy situations. Normally, you’ll want to intentionally ignore the exact type of an object and let the virtual function mechanism implement the correct behavior for that type automatically. On occasion, however, it’s useful to know the exact runtime (that is, most derived) type of an object for which you only have a base pointer. Often this information allows you to perform a special- Part 3 366 z 516 case operation more efficiently or prevent a base-class interface from becoming ungainly. It happens enough that most class libraries contain virtual functions to produce run-time type information. When exception handling was added to C++, it required information about the runtime type of objects. It became an easy next step to build access to that information into the language. This chapter explains what RTTI is for and how to use it. Comment Runtime casts One way to determine the runtime type of an object through a pointer is to employ a runtime cast, which verifies that the attempted conversion is valid. This is useful when you need to cast a base- class pointer to a derived type. Since inheritance hierarchies are typically depicted with base classes above derived classes, such a cast is called a downcast. Consider the following class hierarchy. In the code that follows, the Investment class has an extra operation that the other classes do not, so it is important to be able to know at runtime whether a Security pointer refers to a Investment object or not. To implement checked runtime casts, each class keeps an integral identifier to distinguish it from other classes in the hierarchy. Comment //: C08:CheckedCast.cpp // Checks casts at runtime #include <iostream> #include <vector> #include " /purge.h" using namespace std; class Security { protected: enum {BASEID = 0}; public: virtual ~Security() {} virtual bool isA(int id) { return (id == BASEID); } }; class Stock : public Security { typedef Security Super; protected: enum {OFFSET = 1, TYPEID = BASEID + OFFSET}; public: bool isA(int id) { 367 z 516 return id == TYPEID || Super::isA(id); } static Stock* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Stock*>(s) : 0; } }; class Bond : public Security { typedef Security Super; protected: enum {OFFSET = 2, TYPEID = BASEID + OFFSET}; public: bool isA(int id) { return id == TYPEID || Super::isA(id); } static Bond* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Bond*>(s) : 0; } }; class Investment : public Security { typedef Security Super; protected: enum {OFFSET = 3, TYPEID = BASEID + OFFSET}; public: bool isA(int id) { return id == BASEID || Super::isA(id); } static Investment* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Investment*>(s) : 0; } void special() { cout << "special Investment function\n"; } }; class Metal : public Investment { typedef Investment Super; protected: enum {OFFSET = 4, TYPEID = BASEID + OFFSET}; public: bool isA(int id) { return id == BASEID || Super::isA(id); } static Metal* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Metal*>(s) : 0; } }; int main() { vector<Security*> portfolio; portfolio.push_back(new Metal); portfolio.push_back(new Investment); portfolio.push_back(new Bond); portfolio.push_back(new Stock); for (vector<Security*>::iterator it = portfolio.begin(); it != portfolio.end(); ++it) { Investment* cm = Investment::dynacast(*it); 368 z 516 if(cm) cm->special(); else cout << "not a Investment" << endl; } cout << "cast from intermediate pointer:\n"; Security* sp = new Metal; Investment* cp = Investment::dynacast(sp); if(cp) cout << " it's an Investment\n"; Metal* mp = Metal::dynacast(sp); if(mp) cout << " it's a Metal too!\n"; purge(portfolio); } ///:~ The polymorphic isA( ) function checks to see if its argument is compatible with its type argument (id), which means that either id matches the object’s typeID exactly or that of one of its ancestors in the hierarchy (hence the call to Super::isA( ) in that case). The dynacast( ) function, which is static in each class, calls isA( ) for its pointer argument to check if the cast is valid. If isA( ) returns true, the cast is valid, and a suitably cast pointer is returned. Otherwise, the null pointer is returned, which tells the caller that the cast is not valid, meaning that the original pointer is not pointing to an object compatible with (convertible to) the desired type. All this machinery is necessary to be able to check intermediate casts, such as from a Security pointer that refers to a Metal object to a Investment pointer in the previous example program. Comment Although for most programs downcasting is not needed (and indeed is discouraged, since everyday polymorphism solves most problems in object-oriented application programs), the ability to check a cast to a more derived type is important for utility programs such as debuggers, class browsers, and databases. C++ provides such a checked cast with the dynamic_cast operator. The following program is a rewrite of the previous example using dynamic_cast. Comment //: C08:CheckedCast2.cpp // Uses RTTI’s dynamic_cast #include <iostream> #include <vector> #include " /purge.h" using namespace std; class Security { public: virtual ~Security(){} }; class Stock : public Security {}; class Bond : public Security {}; class Investment : public Security { public: void special() { cout << "special Investment function\n"; } }; class Metal : public Investment {}; int main() { vector<Security*> portfolio; portfolio.push_back(new Metal); portfolio.push_back(new Investment); portfolio.push_back(new Bond); portfolio.push_back(new Stock); [104] 369 z 516 for (vector<Security*>::iterator it = portfolio.begin(); it != portfolio.end(); ++it) { Investment* cm = dynamic_cast<Investment*>(*it); if(cm) cm->special(); else cout << "not a Investment" << endl; } cout << "cast from intermediate pointer:\n"; Security* sp = new Metal; Investment* cp = dynamic_cast<Investment*>(sp); if(cp) cout << " it's an Investment\n"; Metal* mp = dynamic_cast<Metal*>(sp); if(mp) cout << " it's a Metal too!\n"; purge(portfolio); } ///:~ This example is much shorter, since most of the code in the original example was just the overhead for checking the casts. The target type of a dynamic_cast is placed in angle brackets, like the other new-style C++ casts (static_cast, and so on), and the object to cast appears as the operand. dynamic_cast requires that the types you use it with be polymorphic if you want safe downcasts. This in turn requires that the class must have at least one virtual function. Fortunately, the Security base class has a virtual destructor, so we didn’t have to invent some extraneous function to get the job done. dynamic_cast does its work at runtime, of course, since it has to check the virtual function table of objects according to there dynamic type. This naturally implies that dynamic_cast tends to be more expensive than the other new-style casts. Comment You can also use dynamic_cast with references instead of pointers, but since there is no such thing as a null reference, you need another way to know if the cast fails. That “other way” is to catch a bad_cast exception, as follows: Metal m; Security& s = m; try { Investment& c = dynamic_cast<Investment&>(s); cout << " it's an Investment\n"; } catch (bad_cast&) { cout << "s is not an Investment type\n"; } The bad_cast class is defined in the <typeinfo> header, and, like most of the standard library, is declared in the std namespace. Comment The typeid operator The other way to get runtime information for an object is through the typeid operator. This operator returns an object of class type_info, which yields information about the type of object to which it was applied. If the type is polymorphic, it gives information about the most derived type that applies (the dynamic type); otherwise it yields static type information. One use of the typeid operator is to get the name of the dynamic type of an object as a const char*, as you can see in the following example. Comment //: C08:TypeInfo.cpp // Illustrates the typeid operator #include <iostream> #include <typeinfo> using namespace std; [105] 370 z 516 struct PolyBase {virtual ~PolyBase(){}}; struct PolyDer : PolyBase {}; struct NonPolyBase {}; struct NonPolyDer : NonPolyBase {NonPolyDer(int){}}; int main() { // Test polymorphic Types const PolyDer pd; const PolyBase* ppb = &pd; cout << typeid(ppb).name() << endl; cout << typeid(*ppb).name() << endl; cout << boolalpha << (typeid(*ppb) == typeid(pd)) << endl; cout << (typeid(PolyDer) == typeid(const PolyDer)) << endl; // Test non-polymorphic Types const NonPolyDer npd(1); const NonPolyBase* nppb = &npd; cout << typeid(nppb).name() << endl; cout << typeid(*nppb).name() << endl; cout << (typeid(*nppb) == typeid(npd)) << endl; // Test a built-in type int i; cout << typeid(i).name() << endl; } ///:~ The output from this program is struct PolyBase const * struct PolyDer true true struct NonPolyBase const * struct NonPolyBase false int The first output line just echoes the static type of ppb because it is a pointer. To get RTTI to kick in, you need to look at the object a pointer or reference is connected to, which is illustrated in the second line. Notice that RTTI ignores top-level const and volatile qualifiers. With non- polymorphic types, you just get the static type (the type of the pointer itself). As you can see, built- in types are also supported. Comment It turns out that you can’t store the result of a typeid operation in a type_info object, because there are no accessible constructors and assignment is disallowed; you must use it as we have shown. In addition, the actual string returned by type_info::name( ) is compiler dependent. Some compilers return “class C” instead of just “C”, for instance, for a class named C. Applying typeid to an expression that dereferences a null pointer will cause a bad_typeid exception (also defined in <typeinfo>) to be thrown. Comment The following example shows that the class name that type_info::name( ) returns is fully qualified. Comment //: C08:RTTIandNesting.cpp #include <iostream> #include <typeinfo> using namespace std; class One { 371 z 516 class Nested {}; Nested* n; public: One() : n(new Nested) {} ~One() { delete n; } Nested* nested() { return n; } }; int main() { One o; cout << typeid(*o.nested()).name() << endl; } ///:~ Since Nested is a member type of the One class, the result is One::Nested. Comment You can also ask a type_info object if it precedes another type_info object in the implementation-defined “collation sequence” (the native ordering rules for text), using before (type_info&), which returns true or false. When you say, Comment if(typeid(me).before(typeid(you))) // you’re asking if me occurs before you in the current collation sequence. This is useful should you use type_info objects as keys. Comment Casting to intermediate levels As you saw in the earlier program that used the hierarchy of Security classes, dynamic_cast can detect both exact types and, in an inheritance hierarchy with multiple levels, intermediate types. Here is another example. //: C08:IntermediateCast.cpp #include <cassert> #include <typeinfo> using namespace std; class B1 { public: virtual ~B1() {} }; class B2 { public: virtual ~B2() {} }; class MI : public B1, public B2 {}; class Mi2 : public MI {}; int main() { B2* b2 = new Mi2; Mi2* mi2 = dynamic_cast<Mi2*>(b2); MI* mi = dynamic_cast<MI*>(b2); B1* b1 = dynamic_cast<B1*>(b2); assert(typeid(b2) != typeid(Mi2*)); assert(typeid(b2) == typeid(B2*)); delete b2; } ///:~ This example has the extra complication of multiple inheritance (more on this later in this chapter). If you create an Mi2 and upcast it to the root (in this case, one of the two possible roots 372 z 516 is chosen), the dynamic_cast back to either of the derived levels MI or Mi2 is successful. Comment You can even cast from one root to the other: B1* b1 = dynamic_cast<B1*>(b2); This is successful because B2 is actually pointing to an Mi2 object, which contains a subobject of type B1. Casting to intermediate levels brings up an interesting difference between dynamic_cast and typeid. The typeid operator always produces a reference to a static typeinfo object that describes the dynamic type of the object. Thus, it doesn’t give you intermediate-level information. In the following expression (which is true), typeid doesn’t see b2 as a pointer to the derived type, like dynamic_cast does: typeid(b2) != typeid(Mi2*) The type of b2 is simply the exact type of the pointer: typeid(b2) == typeid(B2*) void pointers RTTI only works for complete types, meaning that all class information must be available when typeid is used. In particular, it doesn’t work with void pointers: //: C08:VoidRTTI.cpp // RTTI & void pointers //!#include <iostream> #include <typeinfo> using namespace std; class Stimpy { public: virtual void happy() {} virtual void joy() {} virtual ~Stimpy() {} }; int main() { void* v = new Stimpy; // Error: //! Stimpy* s = dynamic_cast<Stimpy*>(v); // Error: //! cout << typeid(*v).name() << endl; } ///:~ A void* truly means “no type information at all.” Comment Using RTTI with templates Class templates work well with RTTI, since all they do is generate classes. As usual, RTTI provides a convenient way to obtain the name of the class you’re in. The following example prints the order of constructor and destructor calls: Comment //: C08:ConstructorOrder.cpp // Order of constructor calls #include <iostream> #include <typeinfo> [106] 373 z 516 using namespace std; template<int id> class Announce { public: Announce() { cout << typeid(*this).name() << " constructor" << endl; } ~Announce() { cout << typeid(*this).name() << " destructor" << endl; } }; class X : public Announce<0> { Announce<1> m1; Announce<2> m2; public: X() { cout << "X::X()" << endl; } ~X() { cout << "X::~X()" << endl; } }; int main() { X x; } ///:~ This template uses a constant int to differentiate one class from another, but type arguments would work as well. Inside both the constructor and destructor, RTTI information produces the name of the class to print. The class X uses both inheritance and composition to create a class that has an interesting order of constructor and destructor calls. The output is: Comment Announce<0> constructor Announce<1> constructor Announce<2> constructor X::X() X::~X() Announce<2> destructor Announce<1> destructor Announce<0> destructor Multiple inheritance Of course, the RTTI mechanisms must work properly with all the complexities of multiple inheritance, including virtual base classes (discussed in depth in the next chapter—you may want to come back to this after reading Chapter 9): //: C08:RTTIandMultipleInheritance.cpp #include <iostream> #include <typeinfo> using namespace std; class BB { public: virtual void f() {} virtual ~BB() {} }; class B1 : virtual public BB {}; class B2 : virtual public BB {}; class MI : public B1, public B2 {}; int main() { 374 z 516 BB* bbp = new MI; // Upcast // Proper name detection: cout << typeid(*bbp).name() << endl; // Dynamic_cast works properly: MI* mip = dynamic_cast<MI*>(bbp); // Can't force old-style cast: //! MI* mip2 = (MI*)bbp; // Compile error } ///:~ The typeid( ) operation properly detects the name of the actual object, even through the virtual base class pointer. The dynamic_cast also works correctly. But the compiler won’t even allow you to try to force a cast the old way: Comment MI* mip = (MI*)bbp; // Compile-time error The compiler knows this is never the right thing to do, so it requires that you use a dynamic_cast. Comment Sensible uses for RTTI Because it allows you to discover type information from an anonymous polymorphic pointer, RTTI is ripe for misuse by the novice because RTTI may make sense before virtual functions do. For many people coming from a procedural background, it’s difficult not to organize programs into sets of switch statements. They could accomplish this with RTTI and thus lose the important value of polymorphism in code development and maintenance. The intent of C++ is that you use virtual functions throughout your code and that you only use RTTI when you must. Comment However, using virtual functions as they are intended requires that you have control of the base- class definition because at some point in the extension of your program you may discover the base class doesn’t include the virtual function you need. If the base class comes from a library or is otherwise controlled by someone else, a solution to the problem is RTTI: you can derive a new type and add your extra member function. Elsewhere in the code you can detect your particular type and call that member function. This doesn’t destroy the polymorphism and extensibility of the program, because adding a new type will not require you to hunt for switch statements. However, when you add new code in the main body that requires your new feature, you’ll have to detect your particular type. Comment Putting a feature in a base class might mean that, for the benefit of one particular class, all the other classes derived from that base require some meaningless stub for a pure virtual function. This makes the interface less clear and annoys those who must redefine pure virtual functions when they derive from that base class. Comment Finally, RTTI will sometimes solve efficiency problems. If your code uses polymorphism in a nice way, but it turns out that one of your objects reacts to this general-purpose code in a horribly inefficient way, you can pick that type out using RTTI and write case-specific code to improve the efficiency. Comment A trash recycler To further illustrate a practical use of RTTI, the following program simulates a trash recycler. Different kinds of “trash” are inserted into a single container and then later sorted according to their dynamic types. Comment //: C08:Recycle.cpp // A Trash Recycler #include <cstdlib> #include <ctime> [...]... parameterized "mixin" class #include #include "Countable.h" #include "DBConnection2.h" class DBClient { public: DBClient(DBConnection* dbCon) { db = dbCon; db->attach(); } ~DBClient() { db->detach(); } private: DBConnection* db; }; int main() { 388 z 516 DBConnection* db = DBConnection::create("MyDatabase"); assert(db->refCount() == 1); DBClient c1 (db);... parameterized mixin 387 z 516 #ifndef DBCONNECTION_H #define DBCONNECTION_H #include "Database.h" #include #include using std::string; template class DBConnection : public Database, public Counter { public: static DBConnection* create(const string& dbStr) throw(DatabaseError) { DBConnection* con = new DBConnection(dbStr); con->attach(); assert(con->refCount() == 1); return con;... associates pointers to type_info objects with a vector of Trash pointers Since a map requires an ordering predicate, we provide one named TInfoLess that calls type_info::before( ) As we insert Trash pointers into the map, they are associated automatically with their type_info key Comment //: C0 8: Recycle2.cpp // A Trash Recycler #include #include #include #include #include... DBConnection constructor and destructor This makes using a DBConnection easy to use, as the following program shows Comment //: C0 9:UseDatabase2.cpp // Tests the Countable "mixin" class #include #include "DBConnection.h" class DBClient { public: DBClient(DBConnection* dbCon) { db = dbCon; db->attach(); } ~DBClient() { db->detach(); } // Other database requests using db… private: DBConnection*... example Comment //: C0 9:Interfaces.cpp // Multiple interface inheritance #include #include #include using namespace std; class Printable { public: virtual ~Printable() {} virtual void print(ostream&) const = 0; }; class Intable { public: virtual ~Intable() {} virtual int toInt() const = 0; }; class Stringable { public: 3 82 z 516 virtual ~Stringable() {} virtual string toString()... and Countable and provides a static create( ) function that initializes its Countable subobject (This is an example of the Factory Method design pattern, discussed in the next chapter.) Comment //: C0 9:DBConnection.h // Uses a "mixin" class #ifndef DBCONNECTION_H #define DBCONNECTION_H #include "Countable.h" #include "Database.h" #include #include using std::string; class DBConnection... object c1 is destroyed (This is because of Countable’s virtual destructor, as we explained earlier.) Comment A template approach is commonly used for mixin inheritance, allowing the user to specify at compile time which flavor of mixin is desired This way you can use different reference-counting approaches without explicitly defining DBConnection twice Here’s how it’s done Comment //: C0 9:DBConnection2.h... }; int main() { DBConnection* db = DBConnection::create("MyDatabase"); assert(db->refCount() == 1); DBClient c1 (db); assert(db->refCount() == 2) ; DBClient c2 (db); assert(db->refCount() == 3); // Use database, then release attach from original create db->detach(); assert(db->refCount() == 2) ; } ///:~ Comment The call to DBConnection::create( ) calls attach( ), so when we’re finished, we must explicitly... c1 (db); assert(db->refCount() == 2) ; DBClient c2 (db); assert(db->refCount() == 3); db->detach(); assert(db->refCount() == 2) ; } ///:~ Comment The general pattern for multiple parameterized mixins is simply: template class Subject : public Mixin1, public Mixin2, … public MixinK { }; Duplicate subobjects When you inherit from a base class, you get a copy of all... a template solution is more compact: //: C0 9:Interfaces2.cpp // Implicit interface inheritance via templates #include #include #include using namespace std; 383 z 516 class Able { int myData; public: Able(int x) { myData = x; } void print(ostream& os) const { os . type_info key. Comment //: C0 8: Recycle2.cpp // A Trash Recycler #include <cstdlib> #include <ctime> #include <iostream> #include <map> #include <typeinfo> #include. types. Comment //: C0 8: Recycle.cpp // A Trash Recycler #include <cstdlib> #include <ctime> 375 z 516 #include <iostream> #include <typeinfo> #include <vector> #include. such a checked cast with the dynamic_cast operator. The following program is a rewrite of the previous example using dynamic_cast. Comment //: C0 8: CheckedCast2.cpp // Uses RTTI’s dynamic_cast #include

Ngày đăng: 13/08/2014, 09:20