Title: Effective C
1Effective C
- 55 Specific Ways to Improve Your Programs and
Designs
2Why???
- Used without discipline, however, C can lead
to code that is incomprehensible, unmaintainable,
inextensible, inefficient and just plain wrong.
3Whats it all about?
Decisions, Decisions, Decisions
inheritance or templates?
public or private inheritance?
private inheritance or composition?
member or non-member functions?
DESIGN PATTERNS!
4Whats 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?
5Terminology
- 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
6Terminology (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 ..
7Terminology (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)
8Explicit 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
9Copy 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?
10Item 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.
11And now
12Item 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.
13More 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
14Item 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
15Placement 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
16Consider 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?
17May 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
18Physical 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
19Example 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!
20Modifying 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
21Mutable 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
-
22Exercise
23Avoid 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
24Cast-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!
25Item 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.
26Initialization
- 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).
27Initialization (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
28Initialization (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.
29Initialization 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?
30Initialization 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)
31On to
- Chapter Two
- Constructors, Destructors and Assignment Operators
32Item 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)
33What 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
34Item 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).
35Example
- class Uncopyable
- protected
- Uncopyable()
- Uncopyable()
- private
- Uncopyable(const Uncopyable)
- Uncopyable operator(const Uncopyable)
-
- class HomeForSale private Uncopyable
- // class has no copy ctor or operator
-
-
36Item 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!
37TimeKeeper 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.
38Dont 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.
39When 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
40A 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()
41But 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()
42Item 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.
43Example 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
-
44DBConnection (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
45Options 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
-
46A 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
47Item 9
- Never call virtual functions during construction
or destruction - The calls wont do what you expect (differs from
C and Java!)
48Example
- 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.
49Discussion
- Would be easy to catch this one, because pure
virtual so program wouldnt link. - If function were just virtual, wrong version
would be called
50Item 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
-
51Item 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
52Handling 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).
53A 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).
54Item 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. -
55Can 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!
56What 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
57Chapter 3
58Item 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?
59A 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
60The 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
61Another 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.
62Dont 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.
63Item 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
64Options 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.
65Example 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
66Two 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.
67Item 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
68Example
- 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())
69Item 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)
70Item 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())
71Chapter 4
72Item 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
73Example
- 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
74A 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!
75A 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))
76Be 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!!!!
77Item 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)
78More 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)
79Item 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
80Avoid 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()
81Does 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)
82Item 21
- Dont try to return a reference when you must
return an object
Heres an object
Theres nothing there!
83Example
- 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!
84Example, 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
85Example, 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
86Item 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.
87Example
- 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
88What 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.
89Other views on the topic
- http//www.velocityreviews.com/forums/t286009-data
-members-as-quotprotectedquot.html
90Item 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??
91Example 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.
92Convenience 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.
93Item 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
94Making 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!
95Should 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
96Item 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.