Monday, October 01, 2007

Effecitve C++ (21-34)

Item 21: Don't try to return a reference when you must return an object

the function returns a reference to result, but result is a local object, and local objects are destroyed when the function exits.
Several examples are very good to explain 'return type' errors. a class for representing rational numbers, including a function for multiplying two rationals together:
class Rational{
public:
Rational(int numerator = 0; int denominator = 1);
private::
int n, d;
friend const Rational operator* (const Rational& lhs, const Rational& rhs);
};

Implement operator* so that we can do : Rational a(1,2); Rational b(3,4); Rational c=a * b; Check the following error implementations:
const Rational& operator* (const Rational & lhs, const Rational& rhs)
{
Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
return result;
}
{
Rational *result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
return *result;
}
{
static Rational result;
result = ...;
return result;
}

The last one is tricky. Consider: if (operator==(operator*(a, b), operator*(c, d)))
Notice that when operator== is called, there will already be two active calls to operator*, each of which will return a reference to the static Rational object inside operator*. Thus, operator== will be asked to compare the value of the static Rational object inside operator* with the value of the static Rational object inside operator*.

Finally the correct codes:The right way to write a function that must return a new object is to have that function return a new object.
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
"Never return a pointer or reference to a local stack object, a reference to a heap-allocated object, or a pointer or reference to a local static object if there is a chance that more than one such object will be needed."

Item 22: Declare data members private

If data members aren't public, the only way for clients to access an object is via member functions. Using functions gives you much more precise control over the accessibility of data members.
class AccessLevels
{
public:
int getReadOnly() const { return readOnly; } v
oid setReadWrite(int value) { readWrite = value; }
int getReadWrite() const { return readWrite; }
void setWriteOnly(int value) { writeOnly = value; }

private:
int noAccess; // no access to this int
int readOnly; // read-only access to this int
int readWrite; // read-write access to this int
int writeOnly; // write-only access to this int
};

Item 23: Prefer non-member non-friend functions to member functions

A function is used to clean all things:
class WebBrowser
{
public: ...
void clearCache();
void clearHistory();
void removeCookies(); ...
};

which method is preferred?
class WebBrowser
{ public: ...
void clearEverything();
// calls clearCache, clearHistory, // and removeCookies
...
};

or a non-member function that calls the appropriate member functions:
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}

Given a choice between a member function (which can access not only the private data of a class, but also private functions, enums, typedefs, etc.) and a non-member non-friend function (which can access none of these things) providing the same functionality, the choice yielding greater encapsulation is the non-member non-friend function, because it doesn't increase the number of functions that can access the private parts of the class.

Item 24: Declare non-member functions when type conversions should apply to all parameters

Item 25: Consider support for a non-throwing swap

What are you talking about? re-read please.

Item 26: Postpone variable definitions as long as possible

Item 32: Make sure public inheritance models "is-a."

every D is-a B, but not vice versa.

Item 33: Avoid hiding inherited names

class Base
{
private: int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);

virtual void mf2();
void mf3();
void mf3(double);

... };

class Derived: public Base
{
public:
virtual void mf1();
void mf3();

void mf4();
... };

Derived d;
int x; ...
d.mf1(); // fine, calls Derived::mf1
d.mf1(x); // error! Derived::mf1 hides Base::mf1
d.mf2(); // fine, calls Base::mf2
d.mf3(); // fine, calls Derived::mf3
d.mf3(x); // error! Derived::mf3 hides Base::mf3

Names in derived classes hide names in base classes.
This means that if you inherit from a base class with overloaded functions and you want to redefine or override only some of them, you need to include a using declaration for each name you'd otherwise be hiding. If you don't, some of the names you'd like to inherit will be hidden.
To make hidden names visible again, employ using declarations or forwarding functions.
class Derived: public Base
{
public:
using Base::mf1;
// make all things in Base named mf1 and mf3
using Base::mf3;
// visible (and public) in Derived's scope virtual
void mf1();
void mf3(); v
oid mf4(); ...
};
Item 34: Differentiate between inheritance of interface and inheritance of implementation

The conclusions of this item is:
  • Pure virtual functions specify inheritance of interface only.

  • Simple (impure) virtual functions specify inheritance of interface plus inheritance of a default implementation.

  • Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation.

Especially look at the pure virtual function part:
class Shape
{ public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const; ...
};

class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };

Shape is an abstract class; its pure virtual function draw marks it as such. As a result, clients cannot create instances of the Shape class, only of classes derived from it.
The two most salient features of pure virtual functions are that they must be redeclared by any concrete class that inherits them, and they typically have no definition in abstract classes. In one word: The purpose of declaring a pure virtual function is to have derived classes inherit a function interface only.
This makes perfect sense for the Shape::draw function, because it is a reasonable demand that all Shape objects must be drawable, but the Shape class can provide no reasonable default implementation for that function. The algorithm for drawing an ellipse is very different from the algorithm for drawing a rectangle, for example. The declaration of Shape::draw says to designers of concrete derived classes, "You must provide a draw function, but I have no idea how you're going to implement it."

No comments:

Post a Comment