153. Accessing a C++ Object in C Code: Different Access Specifiers
The fourth restriction on the legality of accessing C++ objects from C code states that all the data members of the class must be declared without an intervening access specifier. This means, theoretically, that the memory layout of a class that looks similar to this example might differ from a class that has the same data members, which are declared in the same order, albeit without any intervening access specifiers:
class AnotherDate // has intervening access specifiers
{
private:
int day;
private:
int month;
private:
int year;
public:
//constructor and destructor
AnotherDate(); //current date
~AnotherDate();
//a non-virtual member function
bool isLeap() const;
bool operator == (const Date& other);
};
In other words, for class AnotherDate, an implementation is allowed to place the member 'month' before the member 'day', 'year' before 'month', or whatever. Of course, this nullifies any compatibility with a C struct. However, in practice, all current C++ compilers ignore the access specifiers and store the data members in the order of declaration. So C code that accesses a class object that has multiple access specifiers should work, but there is no guarantee that the compatibility will remain in the future.
154. Static Variable Declarations in Header Files
Ever declared a static variable in the header file at the file scope and had it introduce completely different behavior than you thought it would?
This is because when you include the header file in more than one .CC file, more than one instance of the variable gets created at each .CC file scope. Obviously, you should never declare a static variable at the file scope unless you want to have a copy for each file that includes the header file.
If you want only one instance, declare the static variable at the class scope as follows:
• In globals.h:
•
• Class globalAccess {
• static int globalA;
• };
• In globals.cc:
•
• int globalAccess::globalA = 0;
• In userfiles:
•
• - Include the globals.h
• - Access the globalA by globalAccess::globalA
155. Pointers to Members of a Template Class
You can define a pointer to a class template member. For example, you can use the specialization vector<int>:
typedef void (vector< int >::*v_vi_szt) (size_t); // v_vi_szt is used to hide the unwieldy syntax
v_vi_szt reserve_ptr = &vector< int >::reserve;
The only difference from ordinary pointers to class members is that you are required to use a template specialization, since a template name per se is not a type. In other words, you have to define a separate pointer to member for every specialization used. In the following example, vector <string> specialization is used (no typedef applied in this case):
void (vector< string >::*v_vs_szt) (size_t) = &vector< string >::reserve; // string specialization
156. Inheritance and a Reference to a Base Pointer
A reader posted this question on one of the C++ newsgroups. Assuming B is a base class for D, his program looks like this:
void g (B* p) ;
void f (B*& p); // modifies p
int main ()
{
D* p = NULL ;
g (p) ; // fine
f (p) ; /* error: "cannot convert parameter 1 from 'class D *' to
'class B *& ' a reference that is not to 'const' cannot
be bound to a non-lvalue" */
}
Function f() works as expected, but g() causes a compilation error. The reader wondered what was wrong with the invocation of f() in main(). Can you see what is wrong with it? p is a D*, not a B*, and to convert it to a B*, the implementation creates a temporary pointer (recall that a reference must be bound to a valid object; in this case, a pointer). Now because temporaries are rvalues, and you can't bind an rvalue to a non-const reference, the compiler complains. Declaring f() as follows:
void f (D* & p) { /* modifies p */ }
solves this problem.
157. Declare a Function Template as a Friend of Another Class Template
There are various forms of friend declarations within a class template: a non-template friend, a specialized template friend, and a class template friend. A function template can also be declared as a friend of a class template. For instance, you might add an overloaded operator == function template to test the equality of two Vector objects. By doing so, you ensure that for every specialization of Vector, the implementation will generate a corresponding specialization of the overloaded operator == as well. Unfortunately, the declaration of a function template friend is rather complex. This following example walks you through these intricate steps and demonstrates how to do the declaration.
In order to declare a template function as a friend, you first have to forward declare the class template and the friend function template as follows:
template <class T> class Vector; // class template forward declaration
// forward declaration of the function template to be used as a friend
template <class T> bool operator== (const Vector<T>& v1, const Vector<T>& v2);
Next, you declare the friend function template inside the class body:
template <class T> class Vector
{
public:
//…
friend bool operator==<T> (const Vector<T>& v1, const Vector<T>& v2);
};
Finally, you define the friend function template as follows:
template <class T> bool operator== (const Vector<T>& v1, const Vector<T>& v2)
{
// two Vectors are equal if and only if:
// 1) they have the same number of elements;
// 2) every element in one Vector is identical to the corresponding element in the second one
if (v1.size() != v2.size() )
return false;
for (size_t i = 0; i<v1.size(); i++)
{
if(v1[i] != v2[i])
return false;
}
return true;
}
158. Calling a Member Function From a Destructor
It's perfectly legal to call a member function—virtual or not—from a destructor. However, you should avoid calling a member function that accesses data members that have already been destroyed. For example:
C::~C()
{
delete p;
this->serialize(p); // undefined behavior
}
In addition, the destructor shouldn't call a member function that throws an exception. Remember that throwing an exception from a destructor is a bad idea, because the destructor itself may have been invoked due to another exception (as part of the stack unwinding process). Trying to throw another exception will cause an immediate program termination.
159. Covariant Template Parameters
Suppose you need to define a function that takes a vector object and performs certain operations on it. The function has to be generic, that is, it should handle all instances of std::vector in the same way. The best way to achieve such generic behavior is by defining the function as a template whose parameter covaries with the vector's parameter. In the following example, the insert_one() function template inserts a default-initialized element to a vector, regardless of the element's type:
#include <vector>
using namespace std;
// the parameter T of insert_one() covaries with vector's T
template <class T> void insert_one(vector<T> &v)
{
T t=T(); // ensure built-in types are default-initialized
v.push_back(t);
}
int main()
{
vector <int> vi;
vector <char> vc;
insert_one(vi);
insert_one(vc);
}
160. Overloading Methods
Suppose you are writing a method in a class that accepts a parameter of a given type. Such a method can also be called with an argument of a different type—as long as an implicit conversion exists between the two types (for example, short to int).
class example
{
public:
void method(int parameter);
...
}
int main()
{
example eg;
short pants = 42;
eg.method(pants); // short to int conversion here
...
return 0;
}
It is possible to overload such methods, and by making the overloaded method private, unwanted conversions can be turned into compile time errors. For example:
class example
{
public:
void method(int parameter);
..
private: // reject unwanted conversions
void method(short);
...
}
int main()
{
example eg;
short pants = 42;
eg.method(pants); // Compile time error
...
return 0;
}
You can even use this technique to overload on different signedness of integers. For example:
namespace non_std
{
class string
{
public:
char & operator[](size_t index);
const char & operator[](size_t index) const;
..
private: // reject unwanted conversions
void operator[](signed int);
void operator[](signed int) const;
...
};
}
161. When is virtual inheritance needed?
Multiple inheritance is a powerful and useful feature in C++, but it can lead to a problem known as the DDD or "Deadly Diamond of Derivation", as in this case:
class ElectricAppliance{ int voltage, int Hertz ; public: //...constructor and other useful methods int getVoltage () const { return voltage; } int getHertz() const {return Hertz; } };
class Radio : public ElectricAppliance {...}; class Tape : public ElectricAppliance {...};
class RadioTape: public Radio, public Tape { //multiple inheritance //... }; void main() {
RadioTape rt;
int voltage = rt.getVoltage(); //compilation Error -- ambiguous //call; //two copies getVoltage() exist in rt: //one from Radio and one from Tape. //Also: which voltage value should be //returned? }//end main()
The problem is clear: rt is derived simultaneously from two base classes, each having its own copy of the methods and data members of ElecctricAppliance. As a result, rt has two copies of ElectricAppliance. This is the DDD. However, giving up multiple inheritance will lead to a design compromise. So in cases where reduplication of data and methods from a common base class is undesirable, virtual inheritance should be used:
class Radio : virtual public ElectricAppliance {...}; class Tape : virtual public ElectricAppliance {...}; class RadioTape: public Radio, public Tape { //multiple inheritance };
As a result, class RadioTape contains a single instance of ElectricAppliance shared by Radio and Tape, so there are no ambiguities, no memory waste, and no need to give up the powerful tool of multiple inheritance:
void main() { RadioTape rt; int voltage = rt.getVoltage(); //now OK }//end main()
162. Private Inheritance
When a derived class inherits from a private base, the is-a relation between a derived object and its private base does not exist. For example:
class Mem_Manager {/*..*/};
class List: private Mem_Manager {/*..*/};
void OS_Register( Mem_Manager& mm);
void main()
{
List li;
OS_Register( li ); //compile time error; conversion from List & to Mem_Manager& is inaccessible
}
Private inheritance is like containment. In the example, class List has a private base, Mem_Manager, which is responsible for its necessary memory bookkeeping. However, List is not a memory manager by itself. Therefore, private inheritance is used to block its misuse.
163. Accessing Class Members From the Static Member Function
When a class has Thread entry functions, it is usually declared as static member functions of the class as in the example below:
Class A
{
private:
int i,j,k;
public:
//Thread Entry function
static DWORD WINAPI ThreadFunc(PVOID p);
//other functions
void SomeFunc();
}
The implementation of the function does not allow access to the class members since this is a static function. One solution is to pass the class object itself as a parameter of the thread function and access the class members through the pointer passed.
The thread may be created from SomeFunc()
void SomeFunc()
{
...
...
CreateThread (NULL, 1024, A::ThreadFunc, this, CREATE_SUSPENDED, &_tid);
...
...
}
DWORD A::ThreadFunc(VOID * p)
{
A * pA = (A *)p;
//access members thru pA
pA->i = pA->j + pA->k;
return 0;
}
This approach is difficult since the object has to be de-referenced every time especially if the object is being used heavily inside the function. An easier way is to add a member function DWORD DoThreadFunc() to the class. From the original ThreadFunc, make a call to DoThreadFunc and do all the processing in DoThreadFunc as shown below.
DWORD A::ThreadFunc(VOID * p)
{
return pA->DoThreadFunc();
}
DWORD A::DoThreadFunc()
{
i = j+k;
return 0;
}
From DoThreadFunc(), we can access the class members directly. This will make the code look more readable without unwanted ->s