Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

118 Technique 22: Using Virtual Inheritance

Implementing Virtual Inheritance

Implementing virtual inheritance in your base classes allows you to create an inheritance structure that will permit all other classes that inherit from your base classes to work properly. The following steps take a look at how we can create an inheritance structure that implements virtual inheritance:

1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.

In this example, the file is named ch22.cpp, although you can use whatever you choose.

2. Type the code from Listing 22-3 into your file.

Or better yet, copy the code from the source file on this book’s companion Web site.

LISTING 22-3: BASE-CLASS INHERITANCE

#include <stdio.h> #include <string>

class Object

{

private:

char *name; public:

Object(void)

{

name=NULL;

}

Object(const char *n)

{

setName( n );

}

virtual ~Object()

{

if ( name ) delete name; name = NULL;

}

virtual const char *Name()

{

return name;

}

virtual void setName(const char *n)

{

if ( name ) delete name;

name = NULL;

if ( n )

{

name = new char[strlen(n)+1]; strcpy( name, n );

}

}

};

 

 

class A : public virtual Object

 

1

{

 

public:

 

 

A()

 

 

: Object(“A”)

 

 

{

 

 

}

 

 

virtual ~A()

 

 

{

 

 

}

 

 

};

 

 

class B : public virtual Object

 

2

{

 

public:

B()

: Object(“B”)

{

}

};

class C : public A, public B

{

public:

C()

{

}

void Display()

{

printf(“Name = %s\n”, Name() );

}

};

int main(int argc, char **argv)

{

C c;

c.Display();

}

Correcting the Code 119

The keys to the above code are in the lines marked with 1 and 2. These two lines force the compiler to create only a single Object instance in the inheritance tree of both A and B.

3. Save the code as a file in your code editor and close the editor application.

4. Compile the source-code file with your favorite compiler on your favorite operating system, and then run the resulting executable.

If you have done everything right, you should see the following output:

$ ./a.exe Name = (null)

Oops. This is not what we wanted to see. We were expecting the name of the class. That name should be ‘C’. The next section fixes that — and gives us the type we really wanted.

Correcting the Code

The problem in our simple example comes about because we assumed that the code would naturally follow one of the two paths through the inheritance tree and assign a name to the class. With virtual inheritance, no such thing happens. The compiler has no idea which class we want to assign the name

to, since the values “belong” to the C class, rather than the A and B classes. We have to tell the code what to do. Let’s do that here.

1. Reopen the source-code file created earlier

(called ch22.cpp) and edit it in your code editor.

2. Modify the constructor for the C class as follows:

C()

: Object(“C”)

{

}

3. Recompile and run the program, and you will see the following (correct) output from the application:

$ ./a.exe Name = C

It isn’t always possible to modify the base classes for a given object, but when you can, use this technique to avoid the “dread diamond” (having a class derived from two base classes both of which derive from

a common base class) — and use classes that have common bases as your own base classes.

When you’re designing a class, keep in mind that if you add a virtual method, you should always inherit from the class virtually. This way, all derived classes will be able to override the functionality of that virtual method directly.

23 Creating Overloaded

Operators

Technique

Save Time By

Defining overloaded operators

Rules for creating overloaded operators

Using a conversion operator

Using overloaded operators

Testing your operator

One of the most fascinating abilities that was added to C++ was the power to actually change the way the compiler interpreted the language. Before C++, if you had a class called, say, Foo, and you

wanted to write a method or function to add two Foo objects, you would have to write code similar to the following:

Foo addTwoFoos( const Foo&f1, const Foo& f2)

{

Foo f3;

// Do something to add the two foos (f1 and f2)

return f3;

}

Then you could call the function in your application code like this:

Foo f1(0);

Foo f2(1); Foo f3;

f3 = addTwoFoos(f1,f2);

Overloaded operators permit you to change the basic syntax of the language, such as changing the way in which the plus operator (+) is used. With the addition of overloaded operators, however, you can now write something like this:

Foo

operator+(const

Foo& f1,

{

const Foo& f2 )

1

 

Foo f3;

 

 

// Do something

to add them

}

return f3;

 

 

 

Rules for Creating Overloaded Operators

121

In your code, you can now include statements such as this:

Foo f3 = f1+f2;

 

 

Without the overloaded operator (

 

1), this line

since the compiler

would generate a compile error,

 

 

knows of no way to add two objects of type Foo.

Of course, this power comes with a corresponding price. When you overload operators like this, even the simplest-looking statement can cause problems. Because you can no longer assume that a single line of code results in a single operation, you must step into every line of code in the debugger to trace through and see what is really happening.

Take a look at this simple-looking statement, for example, in which we assign one Foo object to another:

Foo f1=12;

This statement could, conceivably, be hidden within hundreds of lines of code. If an error crops up in the code that processes this simple assignment statement, you have to dig into every one of those lines to find it. So consider: An overloaded operator may be hard to beat for readability. It is more intuitive to say A+B when you mean to add two things than to write add(A,B), but it’s a debugging nightmare. Weigh very carefully the real need for overloading a particular operator against the pain you can cause someone who’s trying to figure out why a side effect in your code caused his program not to work.

Rules for Creating Overloaded Operators

There are four basic rules that you should use when you overload operators in your own classes:

Make sure that the operator does not conflict with standard usage of that operator.

This rule is pretty straightforward. If you overload the plus (+) operator, you still expect the

operator to do something along the line of adding. You wouldn’t expect (for example) to use the plus operator to invert a string; that wouldn’t make sense. It would drive anyone trying to understand the code completely batty.

Make sure that the operator has no unexpected side effects.

This rule isn’t much more complicated. If I’m adding two numbers together, I don’t expect the result of the operation to change either number. For example, suppose we wrote something like

Foo f1(1); Foo f2(2);

Foo f3 = f1+f2;

After these statements are run, you certainly would expect f1 to still contain a 1 and f2 to still contain a 2. It would be confusing and counterintuitive if you added two numbers and found that f1 was now 3 and f2 was now 5.

Make sure that an operator is the only way you can implement the functionality without having an adverse impact on the end user and the maintainer of the code.

This rule is somewhat subjective but easy to understand. If you could easily write an algorithm as a method or function, there would be no reason to overload an operator to perform the algorithm.

Make sure that the operator and all associated operators are implemented.

This rule is fairly important — especially from the perspective of a user. For example, if you implement the plus (+) operator, you’re going to want to implement the plus-equal operator (+=) as well. It makes no sense to the end user to be able to perform the statement

Foo f3 = f1 + f2;

without also being able to perform this one: f2 += f1;

Unfortunately, the converse operation is not always valid. For example, we might be able to add two strings together easily enough, by