Online Book Reader

Home Category

Professional C__ - Marc Gregoire [105]

By Root 1474 0
operator=(const Spreadsheet& rhs);

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

SpreadsheetCell getCellAt(int x, int y);

int getId() const;

protected:

SpreadsheetImpl* mImpl;

};

Code snippet from SeparateImpl\Spreadsheet.h

This class now contains only one data member: a pointer to a SpreadsheetImpl. The public methods are identical to the old Spreadsheet with one exception: The Spreadsheet constructor with default arguments has been split into two constructors because the values for the default arguments were const members that are no longer in the Spreadsheet class. Instead, the SpreadsheetImpl class will provide the defaults.

The implementations of the Spreadsheet methods, such as setCellAt() and getCellAt(), just pass the request on to the underlying SpreadsheetImpl object:

void Spreadsheet::setCellAt(int x, int y, const SpreadsheetCell& inCell)

{

mImpl->setCellAt(x, y, inCell);

}

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

{

return mImpl->getCellAt(x, y);

}

int Spreadsheet::getId() const

{

return mImpl->getId();

}

Code snippet from SeparateImpl\Spreadsheet.cpp

The constructors for the Spreadsheet must construct a new SpreadsheetImpl to do its work, and the destructor must free the dynamically allocated memory. Note that the SpreadsheetImpl class has only one constructor with default arguments. Both normal constructors in the Spreadsheet class call that constructor on the SpreadsheetImpl class:

Spreadsheet::Spreadsheet(const SpreadsheetApplication& theApp, int inWidth,

int inHeight)

{

mImpl = new SpreadsheetImpl(theApp, inWidth, inHeight);

}

Spreadsheet::Spreadsheet(const SpreadsheetApplication& theApp)

{

mImpl = new SpreadsheetImpl(theApp);

}

Spreadsheet::Spreadsheet(const Spreadsheet& src)

{

mImpl = new SpreadsheetImpl(*(src.mImpl));

}

Spreadsheet::~Spreadsheet()

{

delete mImpl;

mImpl = nullptr;

}

Code snippet from SeparateImpl\Spreadsheet.cpp

The copy constructor looks a bit strange because it needs to copy the underlying SpreadsheetImpl from the source spreadsheet. Because the copy constructor takes a reference to a SpreadsheetImpl, not a pointer, you must dereference the mImpl pointer to get to the object itself so the constructor call can take its reference.

The Spreadsheet assignment operator must similarly pass on the assignment to the underlying SpreadsheetImpl:

Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)

{

*mImpl = *(rhs.mImpl);

return *this;

}

Code snippet from SeparateImpl\Spreadsheet.cpp

The first line in the assignment operator looks a little strange. You might be tempted to write this line instead:

mImpl = rhs.mImpl; // Incorrect assignment!

That code will compile and run, but it doesn’t do what you want. It just copies pointers so that the left-hand side and right-hand side Spreadsheets now both possess pointers to the same SpreadsheetImpl. If one of them changes it, the change will show up in the other. If one of them destroys it, the other will be left with a dangling pointer. Therefore, you can’t just assign the pointers. You must force the SpreadsheetImpl assignment operator to run, which only happens when you copy direct objects. By dereferencing the mImpl pointers, you force direct object assignment, which causes the assignment operator to be called. Note that you can only do this because you already allocated memory for mImpl in the constructor.

This technique to truly separate interface from implementation is powerful. Although a bit clumsy at first, once you get used to it you will find it natural to work with. However, it’s not common practice in most workplace environments, so you might find some resistance to trying it from your coworkers. The most compelling argument in favor of it is not the aesthetic one of splitting out the interface but the cost of a full rebuild if the implementation of the class changes. A full rebuild on a huge project might take hours. With stable interface classes, rebuild time is minimized, and concepts like precompiled headers can further reduce build costs. A discussion

Return Main Page Previous Page Next Page

®Online Book Reader