===========================================
we now only treat the possible meaningful advice which can work for classes
Many client errors can be prevented by the introduction of new types.
e.g. in the Date class, we can encapsule the y,m,d in different classes
struct Day { explict Day(int d): val(d){ }; int val; }; //and then we can confine the client's attempt to limited range like this: Date d(Month(3), Day(30), Year(1995));
aside from that, to guarantee the type safety while month is define in [1, 12], we could use functions like static Month Jan()
(this avoid the problematic non-local static initialization.)
my thinking about the static function to replace a non-local static obj: when we init the whole class’s obj, its static objs may haven’t been created yet. it’s a compatible process.
const
. in item 3, we talked about the if(a*b=c)
problem,size()
for all DT in STL.delete
(or sth like this) for clientDesigning good classes is challenging because designing good types is challenging.
const&
aside from built-in types, iterator
s, functional
s, this is a very important and useful rules.
to avoid repeated invacation of ctors and dtors. while remain the master copy unchanged.
effectively solve the problem of base-slicing
which happened when we pass a derived obj to a base ptr
obj of small types sometimes contains not much more than a pointer(which is the impl. of reference), but if we pass by reference, we’d call ctors of all of the units it points to.
small user-class may not perform well dealing with pass-by-val (this is essentially connected to the reason why we pass by reference, not just superficially for being small)
the small class will be enlarged
here don’t be entangled with the efficiency problem,
when deciding between returning a reference and returning an object, your job is to make the choice that offers correct behavior.
some bad code ex:
//friend function: in stack(deleted then)
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
return result;
}
//allocate in heap by 'new': leakage
const Rational& operator*(const Rational &lhs, const Rational &rhs)
{
Rational *result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
return *result;
}
//static obj with global life: there is only ONE obj
const Rational &operator*(const Rational &lhs, const Rational &rhs)
{
static Rational result;
result = ...;
return result;
}
//in the last case, we may think of putting static into vector...there'll still be the cost of dtors and ctors, also global life means some memory unnecessrily occupied, underlying waste in the long run.
// a possible version
inline const Rational operator*(const Rational &lhs, consr Rational &rhs)
{
return Rational(...);
}
it act like the 'static array', while delete unnecessary ones in time
private
name()
sprivate
is the highest control: if we declare a var protected
or public
, then we eliminate or change it back to private
, it’ll be disastrous (piles of codes to rewrite, retest, redocument or recompile)
it affords us the flexibility to change things in a way that affects only a limited number of clients.
thus, we can say that protected
is no more encapsulated than public
.
the non-member non-friend function yields greater encapsulation in the class than a member or friend function.
it can only integrate functions which have been defined, thus with higher restriction.
tips:
functions stated above should be declared in one namespace(where functions it calls are declared)
sometimes we need implicit conversions. such as a rational-computing class
class Rational {
public:
...
const Rational operator*(const Rational& rhs) const;
};
Rational a(1, 8);
Rational ans1 = a * 2 // right
Rational ans2 = 2 * a // wrong
from the bug above, int
won’t automatically converse to Rational
to solve that, we’d created a function that has two param(where the two obj could be conversed to Rational
objs)
from this example, we could see that, if member functions cannot solve the problem, friend function may not be necessary.
(about friend function)Whenever you can avoid friend functions, you should, because, much as in real life, friends are often more trouble than they’re worth.
std::swap()
related to templates. thus, we don’t talked about it too much
we just talked about the pimpl idiom:
to declare a pimpl(pointer to implementation), and if the class is too large, we just need to swap the pointer.