[note] Effective C++ Chapter 4

13406 ワード

Effective C++ Learning Note 4 (undergradute edition)
Designs and Declarations
===========================================
we now only treat the possible meaningful advice which can work for classes
easy and clear Interface with high-tolerance of client errors
type system
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.
restrict the behavior of clients
  • Add const . in item 3, we talked about the if(a*b=c) problem,
  • Avoiding gratuitous incompatibilities with the built-in types: such as using size() for all DT in STL.
  • Automatically delete (or sth like this) for client

  • Treat classes as types
    Designing good classes is challenging because designing good types is challenging.
  • created and destroyed: ctors, dtors, new, delete
  • initialization differ from object assignment
  • passed by value: copy constructor defines how pass-by-value is implemented for a type.
  • restrictions on legal values(in the former item we have just talked about)
  • fit into an inheritance graph
  • type conversions: even a specific conver-function
  • the compatibility with existing functions and objs; standard functions should be disallowed? (copy disallow)
  • interfaces design
  • meaningful?: general and necessary.

  • prefer const&
    aside from built-in types, iterator s, functional s, this is a very important and useful rules.
    why?
    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.
    issues of “unnecessary”
    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
    No reference when return an obj
    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
    

    Declare data members private
  • syntactic consistency: clients just need to call by name() s
  • fine-grained access control: readOnly, writeOnly and readWrite.
  • encapsulation

  • encapsulation private 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 .
    Prefer non-member non-friend functions to member functions
    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:
  • First, this reasoning applies only to non-member non-friend functions.
  • The second thing to note is that just because concerns about encapsulation dictate that a function be a non-member of one class doesn’t mean it can’t be a member of another class(such as a utility class). what we want is high encapsulation

  • functions stated above should be declared in one namespace(where functions it calls are declared)
    Declare non-member functions when type conversions should apply to all parameters
    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.
    effective 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.