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

Mastering Classes and Objects

You must also initialize the reference member in the copy constructor:

Spreadsheet::Spreadsheet(const Spreadsheet& src) :

mTheApp(src.mTheApp)

{

mId = sCounter++; copyFrom(src);

}

Remember that after you have initialized a reference you cannot change the object to which it refers. Thus, you do not need to attempt to assign to references in the assignment operator.

Const Reference Data Members

Your reference members can refer to const objects just as normal references can refer to const objects. For example, you might decide that Spreadsheets should only have a const reference to the application object. You can simply change the class definition to declare mTheApp as a const reference:

class Spreadsheet

{

public:

Spreadsheet(int inWidth, int inHeight, const SpreadsheetApplication& theApp);

// Code omitted for brevity.

protected:

// Code omitted for brevity.

const SpreadsheetApplication& mTheApp;

static int sCounter;

};

It’s also possible to have a static reference member or a static const reference member, but you will rarely find the need for something like that.

More about Methods

C++ also provides myriad choices for methods. This section explains all the tricky details.

Static Methods

Methods, like members, sometimes apply to the class as a whole, not to each object. You can write static methods as well as members. As an example, consider the SpreadsheetCell class from Chapter 8. It has two helper methods: stringToDouble() and doubleToString(). These methods don’t access information about specific objects, so they could be static. Here is the class definition with these methods static:

class SpreadsheetCell

{

public:

199

Chapter 9

// Omitted for brevity

protected:

static string doubleToString(double val); static double stringToDouble(const string& str);

// Omitted for brevity

};

The implementations of these two methods are identical to the previous implementations! You don’t even need to repeat the static keyword in front of the method definitions. However, note that static methods are not called on a specific object, so they have no this pointer, and are not executing for a specific object with access to its non-static members. In fact, a static method is just like a regular function. The only difference is that it can access private and protected static data members of the class and private and protected non-static data members on other objects of the same type.

You cannot access non-static data members inside a static method.

You call a static method just like a regular function from within any method of the class. Thus, the implementation of all methods in SpreadsheetCell can stay the same. Outside of the class, you need to qualify the method name with the class name using the scope resolution operator (as for static members). Access control applies as usual.

You might want to make stringToDouble() and doubleToString() public so that other code outside the class could make use of them. If so, you could call them from anywhere like this:

string str = SpreadsheetCell::doubleToString(5);

Const Methods

A const object is an object whose value cannot be changed. If you have a const or reference to const object, the compiler will not let you call any methods on that object unless those methods guarantee that they won’t change any data members. The way you guarantee that a method won’t change data members is to mark the method itself with the const keyword. Here is a modified SpreadsheetCell class with the methods that don’t change any data member marked const:

class SpreadsheetCell

{

public:

SpreadsheetCell(); SpreadsheetCell(double initialValue);

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

double getValue() const;

void setString(const string& inString); string getString() const;

200

Mastering Classes and Objects

static string doubleToString(double inValue);

static double stringToDouble(const string& inString);

protected:

double mValue; string mString;

};

The const specification is part of the method prototype and must accompany its definition as well:

double SpreadsheetCell::getValue() const

{

return (mValue);

}

string SpreadsheetCell::getString() const

{

return (mString);

}

Marking a method as const signs a contract with client code guaranteeing that you will not try to change the internal values of the object within the method. If you try to declare a method const that actually modifies a data member, the compiler will complain. You also cannot declare a static method const because it is redundant. Static methods do not have an instance of the class so it would be impossible for them to change internal values. const works by making it appear inside the method that you have a const reference to each data member. Thus, if you try to change the data member the compiler will flag an error.

A non-const object can call const and non-const methods. However, a const object can only call const methods. Here are some examples:

SpreadsheetCell myCell(5);

cout << myCell.getValue() << endl; // OK myCell.setString(“6”); // OK

const SpreadsheetCell& anotherCell = myCell;

cout << anotherCell.getValue() << endl; // OK anotherCell.setString(“6”); // Compilation Error!

You should get into the habit of declaring const all methods that don’t modify the object so that you can use references to const objects in your program.

Note that const objects can still be destroyed, and their destructor can be called. You shouldn’t try to mark the destructor const.

Mutable Data Members

Sometimes you write a method that is “logically” const but happens to change a data member of the object. This modification has no effect on any user-visible data, but is technically a change, so the compiler won’t let you declare the method const. For example, suppose that you want to profile your

201

Chapter 9

spreadsheet application to obtain info about how often data is being read. A crude way to do this would be to add a counter to the SpreadsheetCell class that counts each call to getValue() or getString(). Unfortunately, that makes those methods non-const in the compiler’s eyes, which is not what you intended. The solution is to make your new counter variable mutable, which tells the compiler that it’s okay to change it in a const method. Here is the new SpreadsheetCell class definition:

class SpreadsheetCell

{

public:

SpreadsheetCell(); SpreadsheetCell(double initialValue);

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

double getValue() const;

void setString(const string& inString); string getString() const;

static string doubleToString(double inValue);

static double stringToDouble(const string& inString);

protected:

double mValue; string mString;

mutable int mNumAccesses;

};

Here are the definitions for getValue() and getString():

double SpreadsheetCell::getValue() const

{

mNumAccesses++; return (mValue);

}

string SpreadsheetCell::getString() const

{

mNumAccesses++; return (mString);

}

Remember to initialize mNumAccesses in all your constructors!

Method Overloading

You’ve already noticed that you can write multiple constructors in a class, all of which have the same name. These constructors differ only in the number of types of their parameters. You can do the same thing for any method or function in C++. Specifically, you can overload the function or method name by using it for multiple functions, as long as the number or types of the parameters differ. For example, in the SpreadsheetCell class you could rename both setString() and setValue() to set(). The class definition now looks like this:

202