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

Mastering Classes and Objects

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;

string getString() const;

// Remainder of the class omitted for brevity

};

The implementations of the set() methods stay the same. Note that the double constructor that previously called setValue() must now call set(). When you write code to call set(), the compiler determines which version to call based on the parameter you pass: if you pass a string the compiler calls the string version, if you pass a double the compiler calls the double version.

You might be tempted to do the same thing for getValue() and getString(): rename each of them to get(). However, that does not compile. C++ does not allow you to overload a method name based only on the return type of the method because in many cases it would be impossible for the compiler to determine which version of the method to call. For example, if the return value of the method is not captured anywhere, the compiler has no way to tell which version of the method you wanted.

Note also that you can overload a method based on const. That is, you can write two methods with the same name and same parameters, one of which is declared const and one of which is not. The compiler will call the const method if you have a const object and the non-const method if you have a non- const object.

Default Parameters

A feature similar to method overloading in C++ is default parameters. You can specify defaults for function and method parameters in the prototype. If the user specifies those arguments, the defaults are ignored. If the user omits those arguments, the default values are used. There is a limitation, though: you can only provide defaults for a continuous list of parameters starting from the rightmost parameter. Otherwise, the compiler would not be able to match missing arguments to default parameters. Default parameters are most useful in constructors. For example, you can assign default values to the width and height in your Spreadsheet constructor:

class Spreadsheet

{

public:

Spreadsheet(const SpreadsheetApplication& theApp, int inWidth = kMaxWidth,

int inHeight = kMaxHeight); Spreadsheet(const Spreadsheet& src); ~Spreadsheet();

Spreadsheet& operator=(const Spreadsheet& rhs);

203

Chapter 9

void setCellAt(int x, int y, const SpreadsheetCell& inCell); SpreadsheetCell getCellAt(int x, int y);

int getId();

static const int kMaxHeight = 100; static const int kMaxWidth = 100;

protected:

// Omitted for brevity

};

The implementation of the Spreadsheet constructor stays the same. Note that you specify the default parameters only in the method declaration, but not in the definition.

Now you can call the Spreadsheet constructor with one, two, or three arguments even though there is only one noncopy constructor:

SpreadsheetApplication theApp;

Spreadsheet s1(theApp);

Spreadsheet s2(theApp, 5);

Spreadsheet s3(theApp, 5, 6);

A constructor with defaults for all its parameters can function as a default constructor. That is, you can construct an object of that class without specifying any arguments. If you try to declare both a default constructor and a multiargument constructor with defaults for all its parameters, the compiler will complain because it won’t know which constructor to call if you don’t specify any arguments.

Note that anything you can do with default parameters you can do with method overloading. You could write three different constructors, each of which takes a different number of parameters. However, default parameters allow you to write only one constructor to take three different numbers of arguments. You should use the mechanism with which you are most comfortable.

Inline Methods

C++ gives you the ability to recommend that a call to a method or function should not actually be a method or function call. Instead, the compiler should insert the method or function body directly into the code where the method or function call is made. This process is called inlining, and methods or functions that want this behavior are called inline methods or functions. The process is just a safer version of #define macros.

You can specify an inline method or function by placing the inline keyword in front of its name in the function or method definition. For example, you might want to make the setter and accessor methods of the SpreadsheetCell class inline, in which case you would define them like this:

inline double SpreadsheetCell::getValue() const

{

mNumAccesses++; return (mValue);

}

204

Mastering Classes and Objects

inline string SpreadsheetCell::getString() const

{

mNumAccesses++; return (mString);

}

Now, the compiler has to option to replace calls to getValue() and getString() with the actual method body instead of generating code to make a function call.

There is one major caveat: definitions of inline methods and functions must be available in every source file in which they are called. That makes sense if you think about it: how can the compiler substitute the function body if it can’t see the function definition? Thus, if you write inline functions or methods you should place the definitions in a header file along with their prototypes. For methods, this means placing the definitions in the .h file that includes the class definition. This placement is perfectly safe: the linker doesn’t complain about multiple definitions of the same method. It’s just like a #define macro in this sense.

C++ provides an alternate syntax for declaring inline methods that doesn’t use the inline keyword at all. Instead, you place the method definition directly in the class definition. Here is a SpreadsheetCell class definition with this syntax:

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;

};

Many C++ programmers discover the inline method syntax and employ it without understanding the ramifications of making a method inline. First, there are many restrictions on which methods can be inline. Compilers will only inline the simplest methods and functions. If you define an inline method that the compiler doesn’t want to inline, it may silently ignore the directive. Second, inline methods can lead to code bloat. The body of the methods are reproduced everywhere they are called, increasing the size of your program executable. Thus, you should use inline methods and functions sparingly.

205