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

Chapter 9

delete[] mCells;

// Copy the new memory. copyFrom(rhs);

return (*this);

}

Disallowing Assignment and Pass-By-Value

Sometimes when you dynamically allocate memory in your class, it’s easiest just to prevent anyone from copying or assigning to your objects. You can do this by marking your copy constructor and operator= private. That way, if anyone tries to pass the object by value, return it from a function or method, or assign to it, the compiler will complain. Here is a Spreadsheet class definition that prevents assignment and pass-by-value:

class Spreadsheet

{

public:

Spreadsheet(int inWidth, int inHeight); ~Spreadsheet();

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

protected:

bool inRange(int val, int upper);

int mWidth, mHeight; SpreadsheetCell** mCells;

private:

Spreadsheet(const Spreadsheet& src); Spreadsheet& operator=(const Spreadsheet& rhs);

};

When you write code to copy or assign to a Spreadsheet object, the compiler will complain with a message like ‘=’ : cannot access private member declared in class ‘Spreadsheet’.

You don’t need to provide implementations for private copy constructors and assignment operators. The linker will never look for them because the compiler won’t allow code to call them.

Different Kinds of Data Members

C++ gives you many choices for data members. In addition to declaring simple data members in your classes, you can create data members that all objects of the class share, const members, reference members, const reference members, and more. This section explains the intricacies of these different kinds of data members.

194

Mastering Classes and Objects

Static Data Members

Sometimes giving each object of a class a copy of a variable is overkill or won’t work. The data member might be specific to the class, but not appropriate for each object to have its own copy. For example, you might want to give each spreadsheet a unique numerical identifier. You would need a counter that starts at 0 from which each new object could obtain its ID. This spreadsheet counter really belongs to the Spreadsheet class, but it doesn’t make sense for each Spreadsheet object to have a copy of it because you would have to keep all the counters synchronized somehow. C++ provides a solution with static data members. A static data member is a data member associated with a class instead of an object. You can think of static data members as global variables specific to a class. Here is the Spreadsheet class definition, including the new static counter data member:

class Spreadsheet

{

public:

// Omitted for brevity protected:

bool inRange(int val, int upper); void copyFrom(const Spreadsheet& src);

int mWidth, mHeight; SpreadsheetCell** mCells;

static int sCounter;

};

In addition to listing static class members in the class definition, you must allocate them space in a source file, usually the source file in which you place your class method definitions. You can initialize them at the same time, but note that unlike normal variables and data members, they are initialized to 0 by default. Here is the code to allocate space for and initialize the sCounter member:

int Spreadsheet::sCounter = 0;

This code appears outside of any function or method bodies. It’s almost like declaring a global variable, except that the Spreadsheet:: scope resolution specifies that it’s part of the Spreadsheet class.

Accessing Static Data Members within Class Methods

You can use static data members as if they were regular data members from within class methods. For example, you might want to create an mId member of the Spreadsheet class and initialize it from the sCounter member in the Spreadsheet constructor. Here is the Spreadsheet class definition with an mId member:

class Spreadsheet

{

public:

Spreadsheet(int inWidth, int inHeight); Spreadsheet(const Spreadsheet& src); ~Spreadsheet();

Spreadsheet& operator=(const Spreadsheet& rhs);

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

195

Chapter 9

int getId();

protected:

bool inRange(int val, int upper); void copyFrom(const Spreadsheet& src);

int mWidth, mHeight;

int mId; SpreadsheetCell** mCells;

static int sCounter;

};

Here is an implementation of the Spreadsheet constructor that assigns the initial ID:

Spreadsheet::Spreadsheet(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight)

{

mId = sCounter++;

mCells = new SpreadsheetCell* [mWidth]; for (int i = 0; i < mWidth; i++) {

mCells[i] = new SpreadsheetCell[mHeight];

}

}

As you can see, the constructor can access sCounter as if it were a normal member. Remember to assign an ID in the copy constructor as well:

Spreadsheet::Spreadsheet(const Spreadsheet& src)

{

mId = sCounter++; copyFrom(src);

}

You should not copy the ID in the assignment operator. Once an ID is assigned to an object it should never change.

Accessing Static Data Members Outside Methods

Access control specifiers apply to static data members: sCounter is protected, so it cannot be accessed from outside class methods.

However, even though it is protected, you are allowed to assign it a value when you declare space for it in the source file, despite the fact that the code is not inside any Spreadsheet class method. Here is that line of code again:

int Spreadsheet::sCounter = 0;

Const Data Members

Data members in your class can be declared const, meaning they can’t be changed after they are created and initialized. Constants almost never make sense at the object level, so const data members are usually

196

Mastering Classes and Objects

static as well. You should use static const data members in place of global constants when the constants apply only to the class. For example, you might want to specify a maximum height and width for spreadsheets. If the user tries to construct a spreadsheet with a greater height or width than the maximum, the maximum is used instead. You can make the max height and width static const members of the Spreadsheet class:

class Spreadsheet

{

public:

// Omitted for brevity

static const int kMaxHeight;

static const int kMaxWidth;

protected:

// Omitted for brevity

};

Because these members are static, you must declare space for them in the source file. Because they are const, this is your last chance to give them a value:

const int Spreadsheet::kMaxHeight = 100;

const int Spreadsheet::kMaxWidth = 100;

The C++ standard actually permits you to assign static const member variables a value as you declare them in the class file if they are of integral type (such as int or char).

class Spreadsheet

{

public:

// Omitted for brevity

static const int kMaxHeight = 100;

static const int kMaxWidth = 100;

protected:

// Omitted for brevity

};

This capability is useful if you want to use the constant later in your class definition. Although some older compilers fail to support this syntax, most now accept it. In fact, many compilers allow you to omit the extra definition of the static const member in a source file if you initialize it in the class definition, and if you don’t perform any operations on it that require actual storage, such as taking its address.

You can use these new constants in your constructor as shown in the following section of code (note the use of the ternary operator):

Spreadsheet::Spreadsheet(int inWidth, int inHeight) : mWidth(inWidth < kMaxWidth ? inWidth : kMaxWidth),

mHeight(inHeight < kMaxHeight ? inHeight : kMaxHeight)

{

mId = sCounter++;

197