You can declare a template specialization as a friend of a class template. In this example, the class template Vector declares the specialization C<void*> as its friend:
template <class T> class C{/*...*/};
template <class T> class Vector
{
public:
//...
friend class C<void*>; //other specializations are not friends of Vector
};
Each specialization of Vector has the specialization C<void*> as a friend. Note, however, that other specializations of the class template C, for instance C<int>, C<bool> etc., are not friends of Vector.
137. Using dynamic_cast
You can apply operator dynamic_cast only to polymorphic objects (a polymorphic object is one that has at least one virtual member function). This is a requirement of the C++ standard. There are two reasons for this restriction. In order to perform a dynamic cast, the implementation needs to access the runtime type information of the object to which dynamic_cast is applied. This information is retrieved through the object's vptr, and as you probably know, only polymorphic objects have a vptr. However, there's another reason for this restriction—a philosophical one: the type of a non-polymorphic object is static and can be determined at compile time. Therefore, there's not much point in generating runtime type information for non-polymorphic objects in the first place. Note also that some compilers require that you switch on a compiler option in order to enable RTTI support.
138. How Many Member Functions Can a Class Contain?
The C++ standard doesn't specify the maximal number of member functions that a class can have. However, it recommends that it be 4096. Normally, classes don't have more than 15-25 member functions so you never care about this upper limit. However, people who migrate from C to C++ sometimes group a bunch of legacy C functions under a single class. In a recently posted message on one of the C++ newsgroups, a reader complained that his compiler crashed because he tried to compile a class with 2500 (!) member functions in it. Seemingly, this number doesn't exceed the upper limit recommended in the C++ standard. The problem, however, is that debuggers need to store debugging information on every class member. When the number of members is so high, the compiler crashes.
139. Helper Functions
The interface of a class consists of its public members. Usually, private members represent implementation details and are not exposed to other classes and users. Private members can be member functions, not just data members. When is it useful to declare member functions private? Suppose you have a multimedia player class whose public member functions, e.g., play(), zoom() and stop(), call internal member functions, or helper functions, that take care of internal operations such as managing memory buffers, threads and files. These internal operations are not supposed to be used by any other classes or users because they are highly specialized and deal with low-level facilities such as physical devices and memory pages. As you can see these helper functions can vary with each new release or port to another platform. Therefore, they are declared private. Another example, Shape::Draw() can call low-level, platform-dependent functions that initialize screen drivers and define a client area. Declare such internal members private if they are meant to be used only in class Shape.
140. Creating Classes Dynamically
One reader posted the following question: "I have two classes that have the same member functions and data members. However, the member functions perform different operations. Why can't I do something like this:"
void* pCls;
if (cond == true)
pCls =(Class1 *) new Class1;
else
pCls = (Class2 *) new Class2;
pCls->CommonFunc(); //compiler error
On the face of it, there are many reasons why this code snippet refuses to compile (and even if it did compile, it would probably manifest undefined behavior at runtime). Notwithstanding that, dynamic creation of objects is a fundamental feature of object-oriented programming. How can you achieve the desired effect in well-formed C++?
First, the fact that the two classes have member functions with identical names but different functionality cries for inheritance and virtual member functions. This is done by deriving one class from the other or by deriving both of them from an abstract base class. Secondly, void* should be replaced with a pointer to the base class. Not only is this safer but it also eliminates to need for brute-force casts. The result should look like this:
class Base
{
public:
virtual void CommonFunc() = 0;
};
class Class1 : public Base
{
public:
void CommonFunc(); //implementation
};
class Class2 : public Base
{
public:
void CommonFunc();//implementation
};
Base * pb;
if (cond == true)
pb = new Class1;
else
pCls = new Class2;
pCls->CommonFunc(); //now fine
141. Distinguishing Between Copy Ctor And Assignment Operator
Although the copy constructor and assignment operator perform similar operations, they are used in different contexts. The copy constructor is invoked when you initialize an object with another object:
string first "abc";
string second(first); //copy ctor
On the other hand, the assignment operator is invoked when an already constructed object is assigned a new value:
string second;
second = first; //assignment op
Don't let the syntax mislead you: in the following example, the copy ctor, rather than the assignment operator, is invoked because d2 is being initialized:
Date Y2Ktest ("01/01/2000");
Date d1 = Y2Ktest; /* although = is used, the copy ctor
invoked */
142. Killing an Object Prematurely
Sometimes, you need to force an object to destroy itself because its destructor performs an operation needed immediately. For example, when you want to release a mutex or close a file:
void func(Modem& modem)
{
Mutex mtx(modem); // lock modem
Dial("1234567");
/* at this point, you want to release the
modem lock by invoking Mutex's dtor */
do_other_stuff(); //modem is still locked
} //Mutex dtor called here
After the function Dial() has finished, the modem no longer needs to be locked. However, the Mutex object will release it only when func() exits, and in the meantime, other users will not be able to use the modem. Before you suggest to invoke the destructor explicitly, remember that the destructor will be called once again when func() exits, with undefined behavior:
void func(Modem& modem)
{
Mutex mtx(modem); // lock modem
Dial("1234567");
mtx->~Mutex(); // very bad idea!
do_other_stuff();
} //Mutex dtor called here for the second time
There is a simple and safe solution to this. You wrap the critical code in braces:
void func(Modem& modem)
{
{
Mutex mtx(modem); // lock modem
Dial("1234567");
} //mtx destroyed here
//modem is not locked anymore
do_some_other_stuff();
}
143. Declaring an Object With No Default Constructor as a Member of Another Class
Suppose you have a class called A, which doesn't have a default constructor, and you want to embed an instance of this class as a member of another class, B:
class A
{
public:
A (int n); // no default constructor
};
When you instantiate an ordinary object of class A, you pass an argument to its constructor like this:
A a(3); // OK
However, you can't do that when declaring an instance of A as a member of another class:
class B
{
public:
B();
private:
A a(3); // error
};
You should use a member-initialization list in the containing class's constructor to pass the embedded object's argument:
class B
{
public:
B() : a(3) {} // pass argument to embedded object
private:
A a; // no argument here
};
Some classes have both a default constructor and a constructor that takes one or more arguments, (e.g., std::vector). With such classes, you should use a member-initialization list to invoke a constructor that takes arguments:
class Document
{
private:
vector <char> vc; // no argument here
public:
Document() : vc(80){} // similar to vector <char> vc (80)
};
144. A Reference to a Reference is Illegal
What is wrong with this code snippet?
#include <string>
#include <list>
using std::string;
using std::list;
class Node {/*..*/};
int main()
{
list <node&> ln; //error
}
If you try to compile this example, your compiler will issue numerous compilation errors. The problem is that list<T> has member functions that take or return T&. In other words, the compiler transforms <node&> to <node&&>. Because a reference to a reference is illegal in C++, the program is ill-formed. As a rule, you should instantiate templates in the form of list<node> and never as list <node&>.
145. Reducing A Class's Size
Many programmers still use the non-standard, platform dependent BOOL typedef instead of using bool. There are many good reasons why you shouldn't use BOOL; one of them has to do with the size of this type. Unlike bool, which occupies one byte on most platforms, BOOL occupies four bytes. Thus, BOOL causes am unnecessary increase in the size of your classes and structs. Consider the following example:
class Bloated
{
BOOL a;
BOOL b;
BOOL c;
BOOL d;
};
class Slim
{
bool a;
bool b;
bool c;
bool d;
};
Class Bloated occupies 16 bytes in memory. Slim, on the other hand, occupies only four bytes.
146. Blocking Object Copying and Assignment
To block copying and assignment of an object, explicitly declare the assignment operator and copy constructor private. Don't define them, (there's no point in defining them because they can't be invoked anyway): only declare them. In the following example, class A has a public default constructor. In addition, it declares a private copy constructor and assignment operator to ensure that objects of its class can't be copied or assigned to:
class a
{
public:
A(){}
private:
A(const A&); // declared but not implemented
A& operator=(const A&); // ditto
};
int main()
{
A a; // fine, default ctor is public
A b (a); // error, copy ctor is inaccessible
A c; // fine
c = a; // error, assignment operator is inaccessible
}
147. Nested Classes and Friendship
When you declare a nested class as a friend of its containing class, place the friend declaration after the declaration of the nested class, not before:
class A // containing class
{
private:
int i;
public:
class B // nested class declared first
{
public:
B(A & a) { a.i=0;}; // access a private member of class A
};
friend class B;// friend declaration after B's declaration
};
If you place the friend declaration before the nested class's declaration, the compiler will discard it as the friend class has not been seen yet.
148. Accessing Static Class Members
Because static class members are not associated with a particular object of their class, you can access them even if you haven't instantiated any object. In the following example, the static member function read_val() returns the value of the static member n, although no instance of class C exists:
class C
{
private:
static int n;
public:
static int read_val() {return n;}
};
int main()
{
int m = C::read_val(); // no object of type C exists; OK
}
149. Pseudo Destructors
Fundamental types have a pseudo destructor. A pseudo destructor is a syntactic construct whose sole purpose is to satisfy the need of generic algorithms and containers. It is a no-op code and has no real effect on its object.
typedef int N;
int main()
{
N i = 0;
i.N::~N(); //1: pseudo destructor invocation
i = 1; //i was not affected by the invocation of the pseudo destructor
return 0;
}
In the example, N is used as a synonym for int. In the statement numbered 1, the pseudo destructor of the non-class type N is explicitly invoked. Similar to the constructors of fundamental types, pseudo destructors enable writing code without having to know if a destructor actually exists for a given type.
150. Two Flavors of dynamic_cast<>
The operator dynamic_cast<> comes in two flavors: one uses pointers and the other uses references. Accordingly, dynamic_cast<> returns a pointer or a reference of the desired type when it succeeds. When dynamic_cast<> cannot perform the cast, it returns a NULL pointer, or in case of a reference, it throws a std::bad_cast exception:
void f(Shape & shape) { // A pointer dynamic_cast example:
Circle * p = dynamic_cast < Circle *> (&shape); // test whether shape's dynamic type is Circle
if ( p ) { p->fillArea (); } // successful cast; use the resultant pointer
else {} // shape is of a different type than Circle
}
You should always place a reference dynamic_cast<> within a try-block and include a suitable catch-statement:
void f(Shape & shape) { // A reference dynamic_cast example:
try { /* attempt to downcast shape */
Circle& ref = dynamic_cast < Circle &> (shape); // reference version of dynamic_cast<>
ref.fillArea(); //successful cast; use the resultant object
}
catch (std:bad_cast& bc) { }// shape is not a Circle
}
151. Accessing a C++ Object in C Code: A Concrete Example
The C++ Standard guarantees that within every instance of class Date, data members are set down in the order of their declarations (static data members are stored outside the object and are therefore ignored). Consider this declaration of the class Date:
class Date
{
public:
int day;
int month;
int year;
Date(); //current date
~Date();
bool isLeap() const;
bool operator == (const Date& other);
};
There is no requirement that members be set down in contiguous memory regions; the compiler can insert additional padding bytes between data members to ensure proper alignment. However, this is also the practice in C, so you can safely assume that a Date object has a memory layout that is identical to that of this C struct:
struct POD_Date
/* the following struct has memory layout that is identical to a Date object */
{
int day;
int month;
int year;
};
Consequently, a Date object can be passed to C code and treated as if it were an instance of POD_Date. You might be surprised that the memory layout in C and C++ is identical in this case; class Date defines member functions in addition to data members, yet there is no trace of these member functions in the object's memory layout. Where are these member functions stored? C++ treats nonstatic member functions as static functions. In other words, member functions are ordinary functions. They are no different from global functions, except that they take an implicit this argument, which ensures that they are called on an object and that they can access its data members. An invocation of a member function is transformed to a function call, in which the compiler inserts an additional argument that holds the address of the object.
152. Accessing a C++ Object in C Code: Support for Virtual Base Classes
C code should not access objects that have a virtual base class. The reason is that a virtual base is usually represented in the form of a pointer to a shared instance of the virtual subobject. The position of this pointer among user-defined data members is implementation-dependent. Furthermore, the pointer holds a transient value, which can change from one execution of the program to another. Therefore, accessing an object with a virtual base from C code is highly dange
No comments:
Post a Comment