Effective C - PowerPoint PPT Presentation

1 / 96
About This Presentation
Title:

Effective C

Description:

Effective C++ 55 Specific Ways to Improve Your Programs and Designs Why??? Used without discipline, however, C++ can lead to code that is incomprehensible ... – PowerPoint PPT presentation

Number of Views:141
Avg rating:3.0/5.0
Slides: 97
Provided by: Cyndi5
Category:

less

Transcript and Presenter's Notes

Title: Effective C


1
Effective C
  • 55 Specific Ways to Improve Your Programs and
    Designs

2
Why???
  • Used without discipline, however, C can lead
    to code that is incomprehensible, unmaintainable,
    inextensible, inefficient and just plain wrong.

3
Whats it all about?
Decisions, Decisions, Decisions
inheritance or templates?
public or private inheritance?
private inheritance or composition?
member or non-member functions?
DESIGN PATTERNS!
4
Whats it all about? (2nd category)
How do I do this correctly?
should destructor be virtual?
what return type to use?
what should operator do when it cant get enough
memory?
5
Terminology
  • Declarations name type
  • extern int x // object declaration
  • // function declaration, also signature
  • stdsize_t numDigits(int num)
  • class Widget // class declaration
  • // template declaration
  • templatelttypename Tgt
  • class GraphNode

6
Terminology (continued)
  • Definition details
  • int x // object definition memory
  • // function definition - body
  • stdsize_t numDigits(int num)
  • // class and template definitions, include
  • // methods and data
  • class Widget
  • public
  • // list of methods, data
  • template lttypename Tgt
  • class GraphNode
  • public
  • // list of methods, data ..

7
Terminology (continued)
  • Initialization give first value
  • Default constructor no arguments (may be
    constructor with all default arguments)
  • Recommendation make constructor explicit, to
    avoid implicit type conversions (example next
    slide)

8
Explicit Example
  • class A
  • public
  • A()
  • class B
  • public
  • explicit B(int x0,
  • bool btrue)
  • class C
  • public
  • explicit C(int x)
  • // not a default
  • // constructor
  • void doSomething(B bObj)
  • // in main
  • B bObj1
  • doSomething(bObj1) // fine
  • B bObj2(28)
  • doSomething(bObj2) // fine
  • doSomething(28) // error!
  • doSomething(B(28))
  • // fine, uses B constructor
  • // explicitly

9
Copy Constructor/Copy Assignment
  • class Widget
  • public
  • Widget()
  • Widget(const Widget rhs)
  • Widget operator (const Widget rhs)
  • Widget w1 // invoke default constructor
  • Widget w2(w1) // invoke copy constructor
  • w1 w2 // invoke assignment
  • Widget w3 w2 // invoke copy constructor!

Why?
10
Item 1
  • View C as a federation of languages
  • C blocks, statements, preprocessor, arrays,
    pointers, etc.
  • C - OO, classes, encapsulation, inheritance,
    polymorphism, virtual functions
  • Template C - generic programming, often with
    special rules
  • STL containers, iterators, algorithms, function
    objects, using templates and specific conventions
  • Rules for effective C vary, depending on the
    part of C you are using.

11
And now
  • On to some items!

12
Item 2
  • Prefer consts, enums and inlines to defines
    (i.e., prefer compiler to preprocessor)
  • define ASPECT_RATIO 1.653
  • const double AspectRatio 1.653
  • Symbolic names may be removed, dont show up in
    error messages or debugging confusing.
  • Could have multiple copies of 1.653 in object
    code.

13
More on constants
  • class CostEstimate
  • private
  • static const double FudgeFactor
  • const double CostEstimateFudgeFactor 1.35
  • class GamePlayer
  • private
  • enum NumTurns 5
  • int scoresNumTurns

remember static?
some compilers wont allow values
in declaration must provide definition
enum hack if need value for constant and
compiler wont allow
14
Item 3
  • Use const wherever possible
  • char greeting Hello
  • char p greeting
  • // non-const pointer, non-const data
  • const char p2 greeting
  • // non-const pointer, const char data
  • char const p3 greeting
  • // const pointer, non-const data
  • const char const p4 greeting
  • // const pointer, const data

15
Placement of const
  • The following are equivalent
  • void f1 (const Widget pw)
  • void f2 (Widget const pw)
  • Iterators are similar to pointers
  • a const_iterator is like const T iterator can
    point to a different item, but item cant be
    changed
  • a const iterator is like T const iterator
    cant point to a different item, but you can
    change the value of the item that it is pointing
    to

16
Consider making return value const
  • const Rational . . .
  • const Rational operator (const Rational lhs,
  • const Rational rhs)
  • Rational a, b, c
  • if (a b c) . . .

Meant to be What will happen with const?
without? What happens with built-in types?
17
May need two versions
  • Overloaded operator, notice return by , to
    allow modification
  • Cant overload based on return type, but can
    overload based on const vs. non-const member
    function
  • class TextBlock
  • public
  • . . .
  • const char operator(stdsize_t pos) const
  • return textpos
  • char operator(stdsize_t pos)
  • return textpos
  • private
  • stdstring text
  • void print(const TextBlock ctb)
  • stdcout ltlt ctb0 // OK
  • ctb0 A // Not OK compiler error
  • TextBlock tb(hello)

This is required to overload
18
Physical constness vs Logical constness
  • Physical (bitwise) const member function is
    const iff it doesnt modify any of the bits
    inside the object
  • Logical const const member method might modify
    some bits in object, but only in ways clients
    cannot detect
  • Compilers enforce bitwise constness, you should
    program using logical constness

19
Example Problem with bitwise const
  • class CTextBlock
  • public
  • char operator(stdsize_t pos) const
  • return pTextpos
  • private
  • char pText
  • const CTextBlock cctb(Hello) // constant
    object
  • char pc cctb0 // calls constant
    operator
  • pc J // cctb is now Jello

violates logical constness, but compiler allows!
20
Modifying bits client doesnt see
  • class CTextBlock
  • public
  • . . .
  • stdsize_t length() const
  • private
  • char pText
  • stdsize_t textLength // last calculated
    length
  • bool lengthIsValid // whether length is valid
  • stdsize_t CTextBlocklength() const
  • if (!lengthIsValid)
  • // error! changes bits
  • textLength stdstrlen(pText)
  • lengthIsValid true
  • return textLength

21
Mutable to the rescue
  • class CTextBlock
  • public
  • . . .
  • stdsize_t length() const
  • private
  • char pText
  • mutable stdsize_t textLength
  • mutable bool lengthIsValid
  • stdsize_t CTextBlocklength() const
  • if (!lengthIsValid)
  • textLength stdstrlen(pText) // OK now
  • lengthIsValid true
  • return textLength

22
Exercise
  • Begin C Exercise 1

23
Avoid duplication in const/non-const
  • class TextBook
  • public
  • const char operator(stdsize_t pos) const
  • // do bound checking
  • // log access data
  • // verify data integrity
  • return textpos
  • char operator(stdsize_t pos)
  • // do bound checking
  • // log access data
  • // verify data integrity
  • return textpos
  • private
  • stdstring text

lots of duplicate code!
could put duplicated code in a function and call
it but then have duplicated calls to that
function, and duplicated return
24
Cast-Away const
  • class TextBook
  • public
  • const char operator(stdsize_t pos) const
  • // same as before
  • return textpos
  • char operator(stdsize_t pos)
  • return const_castltchargt(
  • static_castltconst TextBlockgt(this)position
    )
  • private
  • stdstring text

Now non-const just calls const
const_cast needed to remove const before return
add const, to call const version of safe
conversion, so use static_cast casts covered in
Item 27
DO NOT go the other direction not safe!
25
Item 4
  • Make sure that objects are initialized before
    theyre used.
  • The rules for when object initialization is
    guaranteed to take place are too complicated to
    be worth memorizing. Better practice to always
    initialize objects before use.

26
Initialization
  • Make sure all constructors initialize everything
    in the object.
  • Assignment is not the same as initialization.
  • ABEntryABEntry(const stdstringname, const
    stdlistltPhoneNumbergt phones)
  • theName name
  • thePhones phones
  • numTimesConsulted 0

default constructors were called for these prior
to entering the body of the constructor thats
when they were initialized. Not true for
built-in types (e.g., numTimesCalled).
27
Initialization (continued)
  • Prefer member initialization lists
  • ABEntryABEntry(const string name, const
    listltPhoneNumbergt phones) theName(name),
    thePhones(phones), numTimesConsulted (0)
  • Single call to copy constructor is more efficient
    than call to default constructor followed by call
    to copy assignment.
  • No difference in efficiency for
    numTimesConsulted, but put in list for consistency

28
Initialization (continued)
  • Can do member initialization lists even for
    default construction
  • ABEntryABEntry() theName(), thePhones(),
    numTimesConsulted (0)
  • Members are initialized in the order they are
    listed in class. Best to list them in that order
    in initialization list.
  • Base classes are always initialized before
    subclasses.

29
Initialization of non-local static objects
  • Better to convert them to local static objects.
  • class FileSystem
  • public
  • stdsize_t numDisks() const
  • . . .
  • extern FileSystem tfs // declaration, must be
    defined
  • // in some .cpp in your library
  • class Directory
  • public Directory(params)
  • DirectoryDirectory(params)
  • stdsize_t disks tfs.numDisks() // use tfs
    object
  • Has tfs been initialized?

30
Initialization of non-local (continued)
  • class FileSystem // as before
  • FileSystem tfs()
  • static FileSystem fs
  • return fs
  • class Directory // as before
  • DirectoryDirectory(params)
  • stdsize_t disks tfs().numDisks()
  • // calls tfs function now

SINGLETON DESIGN PATTERN non-local static objects
replaced with local static object clients call
functions instead of referring to object must
ensure only one copy of FileSystem object
(protected constructor)
31
On to
  • Chapter Two
  • Constructors, Destructors and Assignment Operators

32
Item 5
  • Know what functions C silently writes and
    calls.
  • class Empty
  • becomes
  • class Empty
  • public
  • Empty() ...
  • Empty(const Empty rhs)
  • Empty()
  • Empty operator(const Empty rhs)

33
What do they do?
  • Copy constructor and assignment generally do a
    field-by-field copy.
  • These functions will not be written if your class
    includes a const value or a reference value
    (compiler isnt sure how to handle).
  • template lttypename Tgt
  • class NamedObject
  • public
  • NamedObject(stdstring name, const T value)
  • private
  • stdstring nameValue
  • const T objectValue

34
Item 6
  • Explicitly disallow the use of compiler-generated
    functions you do not want
  • By declaring member functions explicitly, you
    prevent compilers from generating their own
    version.
  • By making a function private, you prevent other
    people from calling it. dont define them, so
    anyone who tries will get a linker error
  • Even better, put functions in parent class, if
    child class attempts to call will generate a
    compiler error (earlier detection is better).

35
Example
  • class Uncopyable
  • protected
  • Uncopyable()
  • Uncopyable()
  • private
  • Uncopyable(const Uncopyable)
  • Uncopyable operator(const Uncopyable)
  • class HomeForSale private Uncopyable
  • // class has no copy ctor or operator

36
Item 7
  • Declare destructors virtual in polymorphic base
    classes.
  • class TimeKeeper
  • public
  • TimeKeeper()
  • TimeKeeper()
  • class AtomicClock public TimeKeeper
  • class WristWatch public TimeKeeper
  • TimeKeeper getTimeKeeper()
  • // returns pointer to dynamically allocated
    object
  • TimeKeeper ptk getTimeKeeper() // AtomicClock
  • // use it in some way
  • delete ptk // release it

THE RESULTS OF THIS OPERATION ARE UNDEFINED!
37
TimeKeeper continued
  • Most likely AtomicClock part of object would not
    be destroyed a partially destroyed object
  • Solution
  • class TimeKeeper
  • public
  • TimeKeeper()
  • virtual TimeKeeper()
  • Any class with virtual functions should almost
    certainly have a virtual destructor.

38
Dont always make it virtual
  • If a class does not contain any virtual
    functions, often indicates its not intended to be
    a base class. Making destructor virtual would be
    a bad idea.
  • class Point
  • public
  • Point(int xCoord, int yCoord)
  • Point()
  • private
  • int x, y
  • Point class can fit in 64-bit register, be passed
    as 64-bit value to other languages (C/Fortran)
  • Virtual functions require objects to carry extra
    info for runtime binding. Typically a vptr
    (virtual table pointer) that points to an array
    of function pointers called a vtbl (virtual
    table).
  • Point class will now be 96 bits (on 32-bit
    architecture)

Handy rule declare a virtual destructor if and
only if that class contains at least one other
virtual function.
39
When not to extend
  • Be careful when you choose to extend a class.
    stdstring contains no virtual functions, so is
    not a good choice for a base class. STL
    container types also do not have virtual
    destructors.
  • class SpecialString public stdstring
  • SpecialString pss new SpecialString(Doomed)
  • stdstring ps
  • ps pss
  • .
  • delete ps // UNDEFINED

Java can prevent programmers from extending a
class C cant
40
A destructor trick
  • Maybe you have a class that you want to be
    abstract, but you dont have any pure virtual
    functions.
  • Make the destructor pure virtual
  • BUT you still have to provide a definition,
    because the compiler always calls the base class
    destructor.
  • class AWOV
  • public
  • virtual AWOV()0
  • AWOVAWOV()

41
But is it really polymorphic?
  • The handy rule for base classes really applies
    only to polymorphic base classes those designed
    to allow manipulation of derived class objects
    through base class interfaces.
  • Not always the case Uncopyable, for example, is
    designed to prevent copying. You wouldnt do
  • Uncopyable uc
  • uc new HomeForSale()

42
Item 8
  • Prevent exceptions from leaving destructors
  • Why? What if you had ten Widgets in an array and
    the Widget for the first Widget threw an
    exception. Then Widget is invoked for the next
    Widget in the array, and it throws an exception.

43
Example DBConnection
  • class DBConnection
  • public
  • // params omitted for simplicity
  • static DBConnection create()
  • void close() // may throw exception
  • class DBConn //manages DBConnection
  • public
  • // destructor ensures db connection always closed
  • DBConn() db.close()
  • private

44
DBConnection (continued)
  • Allows clients to
  • DBConn.dbc(DBConnectioncreate())
  • // use object
  • // destructor called at end of block
  • Problem if db.close() fails, exception will be
    thrown in destructor

45
Options for destructor
  • Terminate the program (OK if program cannot
    continue to run after this type of error)
  • DBConnDBConn()
  • try db.close()
  • catch()
  • make log entry that call failed
  • stdabort()
  • Swallow the exception (usually a bad idea)
  • DBConnDBConn()
  • try db.close()
  • catch()
  • make log entry that call failed

46
A better approach
  • class DBConn //manages DBConnection
  • public
  • void close() // gives client option
  • db.close()
  • closed true
  • DBConn()
  • if (!closed)
  • try db.close() // backup in case client
    didnt
  • catch ( . . )
  • make log entry that call failed
  • // terminate or swallow
  • private
  • DBConnection db
  • bool closed

47
Item 9
  • Never call virtual functions during construction
    or destruction
  • The calls wont do what you expect (differs from
    C and Java!)

48
Example
  • class Transaction // base class
  • public
  • Transaction()
  • virtual void logTransaction() const 0
  • TransactionTransaction()
  • logTransaction() // final act is log
    transaction
  • class BuyTransaction public Transaction
  • public
  • virtual void logTransaction() const
  • class SellTransaction public Transaction
  • public
  • virtual void logTransaction() const
  • BuyTransaction b

at this point, every object is of type
Transaction!
calls Transaction constructor first, so
when logTransaction called NOT one in
BuyTransaction derived class members not
initialized yet, so cant run derived class
functions.
49
Discussion
  • Would be easy to catch this one, because pure
    virtual so program wouldnt link.
  • If function were just virtual, wrong version
    would be called

50
Item 10
  • Have assignment operators return a reference to
    this
  • Assignments can be chained
  • int x, y, z
  • x y z 15
  • Right-associative, so this is like
  • x (y (z 15))
  • Widget operator (const Widget rhs)
  • return this
  • Widget operator (const Widget rhs)
  • return this

51
Item 11
  • Handle assignment to self in operator
  • class Widget . . .
  • Widget w
  • w w
  • OR
  • ai aj //ij, no so obvious
  • OR
  • px py

Could even be pointer to parent class
52
Handling only self assignment
  • Widget Widgetoperator (const Widget rhs)
  • if (this rhs) return this
  • delete pb
  • pb new Bitmap(rhs.pb)
  • return this
  • Handles self-assignment, but has the potential to
    generate an exception (for memory allocation).

53
A better alternative
  • Widget Widgetoperator (const Widget rhs)
  • Bitmap pOrig pb
  • pb new Bitmap(rhs.pb)
  • delete pOrig
  • return this
  • If the call to new throws an exception, pb will
    be unchanged.
  • For self-assignment, a copy is made of original
    before it is deleted. Not the most efficient,
    but it works. Could put if statement back at
    top, if thats a concern (but if its unlikely to
    be often, then better to avoid cost of test and
    branch, which are now required for every call).

54
Item 12
  • Copy all parts of an object
  • class Customer
  • public
  • Customer(const Customer rhs)
  • Customer operator(const Customer rhs)
  • private
  • stdstring name
  • CustomerCustomer(const Customer rhs)
    name(rhs.name)
  • But if a field is added to Customer class, and
    copy constructor and operator not updated,
    copies will be partially constructed.

55
Can be worse with inheritance
  • class PriorityCustomer public Customer
  • public
  • PriorityCustomer(PriorityCustomer rhs)
  • PriorityCustomer operator (PriorityCustomer
    rhs)
  • private
  • int priority
  • PriorityCustomerPriorityCustomer(const
    Customer rhs) priority(rhs.priority)

Not copying base class data members at all!
56
What if base class members are private?
  • PriorityCustomerPriorityCustomer(const
    Customer rhs) Customer(rhs),
    priority(rhs.priority)
  • PriorityCustomer operator (PriorityCustomer
    rhs)
  • Customeroperator(rhs)
  • priorityrhs.priority
  • return this

handy use of initialization list
scope resolution to the rescue
57
Chapter 3
  • Resource Management

58
Item 13
  • Use objects to manage resources.
  • class Investment
  • Investment createInvestment()
  • // factory function, caller must delete pointer
  • // when finished using it
  • void f()
  • Investment pInv createInvestment()
  • delete pInv

Looks OK, but what if premature return? OR,
exception thrown? Even if correct at first, what
happens as code is maintained?
59
A solution..
  • Put the resource returned by createInvestment
    inside an object whose destructor will release
    the resource when control leaves f.
  • stdauto_ptr is a smart pointer whose destructor
    automatically calls delete on what it points to
  • void f()
  • stdauto_ptrltInvestmentgt pInv(createInvestment(
    ))
  • // use pInv as before

call to factory function
will delete pInv via auto_ptrs destructor
60
The idea
  • Resources are acquired and immediately turned
    over to resource-managing objects (e.g., the
    auto_ptr). RAII Resource Acquisition Is
    Initialization.
  • Resource-managing objects use their destructors
    to ensure that resources are released. Will
    happen regardless of how control leaves a block.
  • There should never be more than one auto_ptr
    pointing to an object, or the object will be
    deleted more than once. Copying an auto_ptr sets
    it to NULL, so copying pointer has sole
    ownership.
  • stdauto_ptrltInvestmentgt pInv1(createInvestment()
    )
  • stdauto_ptrltInvestmentgt pInv2(pInv1) // pInv1
    now NULL
  • pInv1 pInv2 // pInv2 now NULL

61
Another alternative
  • Sometimes pointers must have normal copying
    behavior (e.g., in container classes)
  • Use reference-counting smart pointer (RCSP).
    Keeps track of how many objects point to
    resource, deletes it when nobody points to it.
    Similar to garbage collection, but cant break
    cycles.
  • void f()
  • stdtr1shared_ptrltInvestmentgt
    pInv1(createInvestment()) // count1
  • stdtr1shared_ptrltInvestmentgt pInv2(pInv1)
  • // both now point to same object, count 2
  • pInv1 pInv2
  • // still point to same object, count 2

pointers destroyed at end, object count will be 0
so it will be destroyed.
62
Dont use these pointers with arrays
  • auto_ptr and tr1shared_ptr use delete, not
    delete in their destructors
  • Bad idea
  • stdauto_ptrltstdstringgt aps(new
    stdstring10)
  • stdtr1shared_ptrltintgt spi(new int1024)
  • Boost library has classes for dealing with
    arrays.
  • You can also create your own resource-managing
    class.

63
Item 14
  • Think carefully about copying behavior in
    resource-managing classes.
  • Example Create a lock on a mutually exclusive
    item
  • class Lock
  • public
  • explicit Lock(Mutex pm) MutexPtr(pm)
  • lock(mutexPtr)
  • Lock() unlock(mutexPtr)
  • private
  • Mutex mutexPtr
  • Mutex m // define mutex you need to use
  • Lock ml(m) // lock mutex
  • // critical section of code
  • // mutex automatically unlocked

Notice the raw pointer, need to manage
64
Options if try to copy
  • Prohibit copying. Might not make sense to have a
    copy why would you have a copy of a Lock?
  • Reference-count the underlying resource. Hold the
    resource until the last object using it has been
    destroyed.
  • tr1shared_ptr does this. BUT, shared_ptr
    deletes resource when reference count is 0, may
    not be what you want. Can specify deleter as
    second parameter to shared_ptr constructor.

65
Example with shared_ptr
  • class Lock
  • public
  • explicit Lock(Mutex pm)
  • mutexPtr(pm, unlock) // unlock is deleter
  • lock(mutexPtr.get())
  • // get described in Item 15
  • // No Lock needed, due to deleter with
    shared_ptr
  • private
  • stdtr1shared_ptrltMutexgt mutexPtr
  • // no longer a raw pointer

66
Two more options
  • Copy the underlying resource. Only purpose of
    resource-managing class is to ensure resource
    gets deleted. Makes a deep copy.
  • Transfer ownership of the underlying resource.
    Used by auto_ptr in Item 13.

67
Item 15
  • Provide access to raw resources in
    resource-managing classes
  • tr1shared_ptr and auto_ptr overload -gt and to
    allow implicit conversion to underlying raw
    pointer

68
Example
  • class Investment
  • public
  • bool isTaxFree() const
  • Investment createInvestment() // factory
    function
  • return stdauto_ptrltInvestmentgt(new
    Investment())
  • // store result in shared pointer
  • stdtr1shared_ptrltInvestmentgt
    pInv(createInvestment())
  • // pass raw pointer to daysHeld, get returns
    pointer
  • int days daysHeld(pInv.get())
  • // can also use -gt
  • bool taxable !(pInv-gtisTaxFree())
  • // Example with and auto_ptr
  • stdauto_ptrltInvestmentgt pi2(createInvestment())

69
Item 16
  • Use the same form in corresponding uses of new
    and delete
  • MyPoint pointArray new MyPoint100
  • delete pointArray
  • stdstring stringPtr new stdstring
  • delete stringPtr

calls constructor 100 times
behavior undefined probably calls destructor once
behavior undefined probably calls destructor some
number of time (uses bit pattern where
count should be)
70
Item 17
  • Create newed objects in smart pointers in
    standalone statements
  • Example consider two functions
  • int setPriority()
  • void processWidget(stdtr1shared_ptrltWidgetgt
    pw, int priority)
  • Call to processWidget
  • processWidget(stdtr1shared_ptrltWidgetgt(new
    Widget), setPriority())
  • Can leak resources. C compiler can determine
    order. If compiler chooses the following order
  • Execute new Widget
  • Call setPriority
  • Call tr1shared_ptr constructor.
  • What if call to setPriority generates an
    exception? Memory leak! Solution? Create Widget
    and store it, then pass it
  • stdtr1shared_ptrltWidgetgt pw(new Widget)
  • processWidget(pw, setPriority())

71
Chapter 4
  • Designs Declarations

72
Item 18
  • Make interfaces easy to use correctly and hard to
    use incorrectly.

I want to use this class correctly
What mistakes might client make?
client
client
73
Example
  • class Date
  • public
  • Date(int month, int day,int year)
  • Date d(30, 3, 1995) // oops, should be 3, 30!
  • Date d(3, 40, 1995) // just a typo

74
A solution
could also use classes
  • struct Dayexplicit Day(int d)val(d) int val
  • struct Monthexplicit Month(int m)val(m) int
    val
  • struct Yearexplicit Year(int y)val(y) int
    val
  • class Date
  • public
  • Date(const Month m, const Day d, const Year
    y)
  • Date d(30, 3, 1995) // error! wrong types (int
    ! Day)
  • Date d(Day(30), Month(3), Year(1995)) // error!
    wrong types
  • Date d(Month(3), Day(30), Year(1995)) // ok
  • Date d(Month(3), Day(40), Year(2001)) // oops

struct review struct Day int
val structs can have constructors, too!
75
A way to restrict values
  • class Month
  • public
  • static Month Jan() return Month(1)
  • static Month Feb() return Month(2)
  • static Month Dec() return Month(12)
  • private
  • // prevent creation of new Month values
  • explicit Month(int m)
  • Date d(MonthMar(), Day(30), Year(1995))

76
Be consistent with built-in types
  • Few characteristics lead to interfaces that are
    easy to use correctly as much as consistency
  • Few characteristics lead to aggravating
    interfaces as much as inconsistency.

C STL every container has size
Why did it do that? (T
Java array length property Java List size
method Java ArrayList Count property AARGH!!!!
77
Item 19
  • Treat class design as type design
  • How should objects of your new type be created
    and destroyed?
  • How should object initialization differ from
    object assignment?
  • What does it mean for objects of your new type to
    be passed by value?
  • What are the restrictions on legal values for
    your new type? (invariants, exceptions)
  • Does your new type fit into an inheritance graph?
    (virtual/non-virtual)
  • What kind of type conversions are allowed for
    your new type? (implicit/explicit)

78
More type considerations
  • What operators and functions make sense for your
    new type? (member/non-member)
  • What standard functions should be disallowed?
  • Who should have access to members of your new
    type? (friends/nested)
  • What is the undeclared interface of your new
    type? (performance, safety, resource usage)
  • How general is your new type? (templates)
  • Is a new type really what you need? (maybe a few
    non-member functions would do)

79
Item 20
  • Prefer pass-by-reference-to-const to
    pass-by-value

Student plato
class Person public Person() virtual
Person() private stdstring name
stdstring address class Student public
Person public Student() virtual
Student() private stdstring school
stdstring schoolAddr
pass by value
construct Person construct name construct
address construct Student construct
school construct schoolAddr
bool validateStudent(Student)
destruct Person destruct name destruct
address destruct Student destruct school destruct
schoolAddr
80
Avoid the slicing problem
  • When derived class object is passed by value as
    base class object, special features of child
    class are sliced off

void printNameDisplay(Window w) stdcout ltlt
w.name() w.display()
class Window public stdstring name()
const virtual void display() const class
ScrollWindowpublic Window public virtual
void display() const
calls Window display
ScrollWindow sw
calls ScrollWindow display
void printNameDisplay(const Window w)
stdcout ltlt w.name() w.display()
81
Does size matter?
  • Even small objects should be passed by const
    reference
  • Some compilers wont put objects in registers,
    even if only data is one double
  • Size of object may change
  • May be only a pointer, but then will need to copy
    everything pointed to
  • Different implementation of C may be larger
    (some implementations of standard library string
    type are seven times as big as others)

82
Item 21
  • Dont try to return a reference when you must
    return an object

Heres an object
Theres nothing there!
83
Example
  • class Rational
  • public
  • Rational(int num0, int denom0)
  • private
  • int n, d
  • friend const Rational operator(const Rational
    lhs, const Rational rhs)

Is return-by-value necessary?
//Bad option const Rational operator
Rational result(lhs.nrhs.n, lhs.d rhs.d)
return result
  • Rational a(1, 2)
  • Rational b(3, 5)
  • Rational c a b // 3/10

Must create c
Returns pointer to local variable, which wont
exist!
84
Example, continued
// Another bad option const Rational
operator Rational result new
Rational(lhs.nrhs.n, lhs.d rhs.d) return
result
Still have constructor call, which you wanted to
avoid, AND who will call delete??
Rational w, x, y, z w x y z
Two calls to , no way to get at pointers, even
if want to be conscientious.
bool operator(const Rational lhs, const
Rational rhs) Rational a, b, c, d if ((a
b) (c d))
// Yet another bad option const Rational
operator static Rational result result
return result
Always evaluates to true. if (operator(operator
(a, b), operator(b, c))
static static
85
Example, concluded
  • 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.nrhs.n, lhs.drhs.d)

compiler may optimize, to avoid cost of
constructor/destructor
86
Item 22
  • Declare data members private
  • Why not public members?
  • consistency. If everything is a function, wont
    have to remember when to use ()
  • control. Can provide no access, read-only access,
    or read-write access.
  • encapsulation. implementation can change, but
    interface remains the same. no changes needed to
    client code.

87
Example
  • class SpeedDataCollection
  • public
  • void addValue(int speed)
  • double averageSoFar() const
  • Should averageSoFar return precomputed value, or
    compute its value each time its called?
  • If needed infrequently computing each time may
    be OK.
  • If needed frequently and memory is not an issue,
    should precompute and store, so just return a
    value.
  • Either way, decision should be hidden from
    client.

PUBLIC UNENCAPSULATED UNCHANGEABLE
88
What about protected?
  • Are protected data members more encapsulated than
    public ones?
  • Practically speaking, no. Encapsulation is
    inversely proportional to the amount of code that
    might be broken if that data member changes
    (e.g., is removed from class).
  • For public member, thats an unknowably large
    amount.
  • For protected members which are accessed in
    derived classes, still an unknowably large
    amount. In general, too much code will need to
    be rewritten, recompiled etc. if you change that
    data member.

89
Other views on the topic
  • http//www.velocityreviews.com/forums/t286009-data
    -members-as-quotprotectedquot.html

90
Item 23
  • Prefer non-member non-friend functions to member
    functions

class WebBrowser public void clearCache()
void clearHistory() void removeCookies()
void clearBrowser()
We want to clear cache, history and cookies in
the browser. Would this be better as a member
function? Or a non-member function?
void clearBrowser(WebBrowser wb)
wb.clearCache() wb.clearHistory()
wb.removeCookies()
Non-member offers more encapsulation and more
flexibility. How??
91
Example continued
  • Encapsulation
  • Something thats encapsulated is hidden from view
  • More something is encapsulated, fewer things can
    see it
  • Fewer things can see it, greater flexibility we
    have to change it
  • Less code that can see (i.e., access) data the
    more data is encapsulated, so the more freely we
    can change data characteristics
  • Coarse-grained measure count the number of
    functions that access it.
  • Number of functions that can access private data
    is number of member functions number of friend
    functions.
  • Adding a non-member function doesnt add to
    number of functions that can potentially see
    private data.

92
Convenience functions
  • Convenience functions may be grouped into
    different header files
  • May all be in the same namespace

namespace WebBrowserStuff class WebBrowser
.. // core functionality //header
webbrowserbookmarks.h namespace WebBrowserStuff
..//bookmark-related convenience
fns //header webbrowsercookies.h namespace
WebBrowserStuff ..//cookie-related
convenience fns
Partitioning functionality in this way is not
possible when it comes from a classs member
functions.
93
Item 24
  • Declare non-member functions when type
    conversions should apply to all parameters
  • For some classes allowing implicit type
    conversions makes sense e.g., from integers to
    rationals

class Rational public Rational(int
numerator0, denominator1) int numerator()
const return n int denominator() const
return d private int n,d
ctor is not explicit
94
Making the case for non-member
  • class Rational
  • public
  • const Rational operator (const Rational rhs)
    const

Remember const return wont allow (abc) when
want (abc)
Rational oneEighth(1,8) Rational
oneHalf(1,2) Rational result oneHalf
oneEighth result oneHalf 2 // OK result
2 oneHalf // error!
result oneHalf.operator (2) // OK result
2.operator (oneHalf) // error!
95
Should it be a friend?
  • class Rational
  • public
  • const Rational operator (const Rational lhs,
    const Rational rhs)
  • Whenever you can avoid friend functions, you
    should. Why? Same reasons that you make
    variables private and as many functions as
    possible non-members

96
Item 25
  • Consider support for a non-throwing swap.
  • Typical swap algorithm (stdswap)
  • template lttypename Tgt
  • void swap(T a, T b)
  • T temp(a)
  • a b
  • b temp
  • Requires 3 copies a to temp, b to a, temp to b
  • Copying not required for types consisting of
    pointer to another type containing the real data
  • Read the book if you need this.
Write a Comment
User Comments (0)
About PowerShow.com