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

Chapter 9

Nested Classes

Class definitions can contain more than just methods and members. You can also write nested classes and structs, declare typedefs, or create enumerated types. Anything declared inside a class is in the scope of that class. If it is public, you can access it outside the class by scoping it with the ClassName:: scope resolution syntax.

You can provide a class definition inside another class definition. For example, you might decide that the SpreadsheetCell class is really part of the Spreadsheet class. You could define both of them like this:

class Spreadsheet

{

public:

class SpreadsheetCell

{

public:

SpreadsheetCell(); SpreadsheetCell(double initialValue);

SpreadsheetCell(const string& initialValue); SpreadsheetCell(const SpreadsheetCell& src); SpreadsheetCell& operator=(const SpreadsheetCell& rhs); void set(double inValue);

void set(const string& inString);

double getValue() const {mNumAccesses++; return (mValue); } string getString() const {mNumAccesses++; return (mString); }

static string doubleToString(double inValue);

static double stringToDouble(const string& inString);

protected:

double mValue; string mString;

mutable int mNumAccesses;

};

Spreadsheet(const SpreadsheetApplication& theApp, int inWdith = kMaxWidth, int inHeight = kMaxHeight);

Spreadsheet(const Spreadsheet& src); ~Spreadsheet();

Spreadsheet& operator=(const Spreadsheet& rhs);

// Remainder of Spreadsheet declarations omitted for brevity

};

Now, the SpreadsheetCell class is defined inside the Spreadsheet class, so anywhere you refer to a SpreadsheetCell outside of the Spreadsheet class you must qualify the name with the Spreadsheet:: scope. This applies even to the method definitions. For example, the default constructor now looks like this:

206

Mastering Classes and Objects

Spreadsheet::SpreadsheetCell::SpreadsheetCell() : mValue(0), mNumAccesses(0)

{

}

This syntax can quickly become clumsy. For example, the definition of the SpreadsheetCell assignment operator now looks like this:

Spreadsheet::SpreadsheetCell& Spreadsheet::SpreadsheetCell::operator=(

const SpreadsheetCell& rhs)

{

if (this == &rhs) { return (*this);

}

mValue = rhs.mValue; mString = rhs.mString;

mNumAccesses = rhs.mNumAccesses; return (*this);

}

In fact, you must even use the syntax for return types (but not parameters) of methods in the Spreadsheet class itself:

Spreadsheet::SpreadsheetCell Spreadsheet::getCellAt(int x, int y)

{

SpreadsheetCell empty;

if (!inRange(x, mWidth) || !inRange(y, mHeight)) { return (empty);

}

return (mCells[x][y]);

}

You can avoid the clumsy syntax by using a typedef to rename Spreadsheet::SpreadsheetCell to something more manageable like SCell:

typedef Spreadsheet::SpreadsheetCell SCell;

This typedef should go outside the Spreadsheet class definition, or else you will have to qualify the typedef name itself with Spreadsheet:: to get Spreadsheet::SCell. That wouldn’t do you much good!

Now you can write your constructor like this:

SCell::SpreadsheetCell() : mValue(0), mNumAccesses(0)

{

}

Normal access control applies to nested class definitions. If you declare a private or protected nested class, you can only use it inside the outer class.

You should generally use nested class definitions only for trivial classes. It is really too clumsy for something like the SpreadsheetCell class.

207

Chapter 9

Friends

C++ allows classes to declare that other classes or nonmember functions are friends, and can access protected and private data members and methods. For example, the SpreadsheetCell class could specify that the Spreadsheet class is its “friend” like this:

class SpreadsheetCell

{

public:

friend class Spreadsheet;

// Remainder of the class omitted for brevity

};

Now all the methods of the Spreadsheet class can access the private and protected data and members of the SpreadsheetCell class.

Similarly, you can specify that one or more functions or members of another class are friends. For example, you might want to write a function to verify that the value and the string of a SpreadsheetCell object are really in synch. You might want this verification routine to be outside the SpreadsheetCell class to model an external audit, but the function should be able to access the internal data members of the object in order to check it properly. Here is the SpreadsheetCell class definition with a friend checkSpreadsheetCell() function:

class SpreadsheetCell

{

public:

// Omitted for brevity

friend bool checkSpreadsheetCell(const SpreadsheetCell &cell);

// Omitted for brevity

};

The friend declaration in the class serves as the function’s prototype. There’s no need to write the prototype elsewhere (although it’s harmless to do so).

Here is the function definition:

bool checkSpreadsheetCell(const SpreadsheetCell &cell)

{

return (SpreadsheetCell::stringToDouble(cell.mString) == cell.mValue);

}

You write this function just like any other function, except that you can directly access private and protected data members of the SpreadsheetCell class. You don’t repeat the friend keyword on the function definition.

friend classes and methods are easy to abuse; they allow you to violate the principle of abstraction by exposing internals of your class to other classes or functions. Thus, you should use them only in limited circumstances such as operator overloading.

208