Effective C++ | 01 Accustom yourself to C++
Effective C++ | 01 Accustom yourself to C++
Book: Effective C++ (Third Edition)
Item 01: View C++ as a federation of languages
C++ is multiparadigm programming language:
- procedural
- object-orientated
- functional
- generic
- metaprogramming features
C++ is not a single language.
C++ is a federation of languages:
- C
- Object-Orientated C++
- Template C++
- STL
Pass by value (C)
More efficient that pass by reference for built-in (C-like) types
Pass by reference to const (Object-Oriented, Template)
Better for user-defined objects (with constructors / destructors)
Esp. true for template C++ (you don’t know type you’re dealing)
Pass by value (STL)
Better for STL because iterators and function objects are modelled on pointers in C
Iterators and function objects in STL pass by value is better
Item 02: Prefer consts, enums, and inlines to #defines
Prefer compiler to the preprocessor
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
The latter is better.
AspectRatio seen by compilers and entered into symbol tables
Also 1.653 (#define) multiple times in object code
Whereas constant AspectRatio appears once
2x SPECIAL CASES
01.Defining constant pointers
Constant definitions in headers files means pointer declared const plus what pointer points to
const char* const authorName = “Scott Meyers”;
NB: string prob better than char*
const std:: authorName("Scott Meyers");
02.Class-specific constants
Limit scope of constant to class = make it a member
Ensure at most one copy of constant = make it static
// GamePlayer.h
class GamePlayer
{
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns];
};
C++ requires definition for declaration except class-specific constants that are static of integral type
(int, char, bool)
NB: if you take address then compiler insists on definition:
// GamePlayer.cpp
const int GamePlayer::NumTurns; // definition no value!
IMP
Cannot create class-specific constant using #define
#define does not respect scope: can’t be private
If (old) compiler does not allow initialization in declaration (above) then do this
// GamePlayer.h
class GamePlayer
{
private:
static const int NumTurns; // constant declaration
};
// GamePlayer.cpp
const int GamePlayer::NumTurns = 5; // definition no value!
However, now scores array won’t work 'cos you need the value of a class constant during compilation:
compilers insist on knowing the size of the array
Only way to forbid in-class specification of initial values for static integral class constants:
ENUM HACK
class GamePlayer
{
private:
enum { NumTurns = 5 }; // "the enum hack" ?makes NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
};
Enum hack used a lot!
Acts more like #define than const
Why?
Take address of const——Legal
Take address of enum——Illegal
Take address of #define——Illegal
If you don’t want people get pointer or reference to integral constants then enum enforces that constraint
Also, enum hack is fundamental technique of template metaprogramming
MACROs
Macros look like functions but don’t incur overhead of function call
Arrgh : must parenthesize all arguments in the macro body!
Do not code like this:
// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice
CALL_WITH_MAX(++a, b+10); // a is incremented once
this is right:
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
No need to
parenthesize parameters inside function body
evaluate parameters multiple times
callWithMax obeys scope and access rules
Can inline function private to the class
Can’t do that with Macro!
SUMMARY
- For simple constants, prefer const or enums to #define
- For function-like macros, prefer inline functions to #define
Item 03: Use const whenever possible
An object should not be modified ?and compilers will enforce that constraint.
Pointers
Specify whether pointer is const, data it points to is const, both or neither
char greeting[] = "Hello";
char stevepro[] = "Steve";
char* p1 = greeting; // neither const
const char* p2 = greeting; // DATA const
char* const p3 = greeting; // POINTER const
const char* const p4 = greeting; // both const
TRICK
Imagine line down the “*”
If const to left then DATA is const
If const to right then POINTER is const
const LHS = DATA (D)
const RHS = POINTER (P)
EXAMPLE
Data p2[0] = 'X';
error C3892: ‘p2’ : you cannot assign to a variable that is const
Pointer p3 = stevepro;
error C3892: ‘p3’ : you cannot assign to a variable that is const
If const on left of asterisk then what’s pointed to is const i.e. data
If const on right of asterisk then the pointer itself is const i.e. pointer
DATA
Both are the same
const char* p2 = greeting;
char const* p2 = greeting;
char const *p2 = greeting;
ITERATORS
STL iterators modelled on (C) pointers
Act like T* pointer
const iterator is like pointer const (T* const pointer)
i.e. what iterator points to (RHS of *) POINTER
Thus, the iterator is not allowed to point to anything else
But the DATA i.e. the thing iterator points to may be modified
Example #1
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;
//++iter; ERROR because iterator is const pointer thus cannot point to anything else!
const_iterator
If you want iterator to point to something else (be modified) but the DATA each iteration points to
Then use const_iterator (const T* pointer)
Example #2
std::vector<int>::const_iterator cIter = vec.begin();
//*cIter = 10; // error C3892: 'cIter' : you cannot assign to a variable that is const
++cIter;
FUNCTION DECLARATION
const refer to:
return value
input parameters
member functions as a whole
GOOD Example
class Rational { … };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Why should return type operator* be const?
Because it prevents this:
Rational a, b, c;
(a * b) = c // wouldn't proactively do this
BUT
if (a * b = c) ... // oops, meant to do comparison
One of the hallmarks of good user-defined types is that they avoid gratuitous incompatibilities with built-in types
Declaring operator* return value as const prevents this DO IT!
const save from annoying “I meant to type ‘==’ but accidentally typed ‘=’” mistakes
const Member Functions
const Member Functions identify which member functions may be invoked on const objects.
2x REASONS important
01.Interface easier to understand:
Which functions modify an object + which don’t
02.Make it possible to work with const objects
IMP: improve performance by pass objects by reference to const
Technique only viable if there are const member functions to manipulate const-qualified objects
IMPORTANT C++ feature:
Member functions differing only in constness can be overloaded
// operator[] for const objects.
const char& operator[](std::size_t position) const
{
return text[position];
}
// operator[] for non-const objects.
char& operator[](std::size_t position)
{
return text[position];
}
tb[0] = 'x';
ctb[0] = 'x'; // error! - writing to const TextBlock
Error here has to do with return type of operator[]
calls to operator[] are fine
but attempt to assign to const char& arises because return type of const version of operator[]
IMPORTANT
non-const operator[] is a reference to a char - a char would not do!
If operator[] on non-const did return simple char then statements like this wouldn’t compile:
char operator[](std::size_t position);
// error C2106: '=' : left operand must be l-value
Why?
Because it never legal to modify the return value of a function that returns a built in type (char)
If it were legal, C++ return objects by value thus a COPY of tb.text[0] would be modified
NOT tb.text[0] itself - not the behavior you want
Member functions as const
2x NOTIONS
01.Physical constness (bitwise constness)
If, and only if, member function doesn’t modify any of the object data members (excluding static)
const member function isn’t allowed to modify any of the non-static data members of the object
Violations: just look for assignments to data members
A const member function isn’t allowed to modify any of the non-static data members
1.5. Many member functions don’t act very const but pass the bitwise test
e.g.
Member function that modifies what a pointer points to (data) doesn’t act const
If the pointer is in the object then the compiler doesn’t complain WTF
const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J'; // Access violation but sometimes may be "Jello"
Problem: create const object but can modify some of its bits in the object!
02.Logical constness
A const member function might modify some of the bits in the object on which it’s invoked
But only in ways that clients cannot detect
std::size_t length() const;
NOT bitwise const as both textLength and lengthIsValid try to be modified
// error C3490: ‘textLength’ cannot be modified because it is being accessed through a const object
It should be valid for const CTextBlock objects. Compilers disagree.
They insist on bitwise constness. What to do? MUTABLE!!
class CTextBlock3
{
public:
CTextBlock3::CTextBlock3(char* t) : pText(t), lengthIsValid(false)
{
}
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
mutable is nice solution to bitwise-constness problem
However, you may need to do similar code in const and non-const version operator[] = code duplication
Put common code into private separate member function that both version operator[] call
But may be better to implement functionality once and use it twice:
One version calls the other (cast away constness)
Have the non-const operator[] call the const version is a safe way to avoid code duplication
even though it requires a cast
// operator[] for const objects.
const char& operator[](std::size_t position) const
{
return text[position];
}
// operator[] for non-const objects.
char& operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
Non-const version has 2x casts
As there’s no way to specify call to the const operator we cast *this from TextBlock& to const TextBlock&
Yes, we have to a cast to const! (Otherwise non-const calls itself recursively forever)
- to add const to *this (call const operator[]) static_cast<const TextBlock&>(*this)[position]
- remove const from const operator[]'s value const_cast<char&>(…) cast away constness
Finally, you do NOT want const version to call the non-const version as non-const may change things in object
(and const promises never to change anything)
But non-const can do whatever it wants and calling const member function imposes no risk
That’s why static_cast works on *this (there’s no const-related danger)
REMEMBER
-
Declaring something const helps compilers detect usage errors.
const can be applied to objects at any scope, to function parameters and return types, and to member functions -
Compilers enforce bitwise constness but you should program using conceptual constness
-
When const and non-const member functions have essentially identical implementations,
code duplication can be avoided by having the non-const version call the const version
04 Item04
09-Nov-2013
Item 04: Make sure that objects are initialized before they’re used
int x;
x guaranteed to be initialized (to zero)
Point p;
p’s data members are sometimes guaranteed to be initialized (to zero)
but sometimes they’re not
Reading uninitialized values yields undefined behavior
Rules that describe object initialization are complicated
Not worth memorizing
e.g.
C part of C++ and initialization incur runtime cost then not guaranteed to take place
Explains why array (C part of C++) not guaranteed to have its contents initialized
But vector (STL part of C++) is
Solution: ALWAYS initialize your objects before you use them
Examples:
int x = 0; // manual initialization of an int
const char* text = "A C-style string" // manual initialization of a pointer
double d;
std::cin >> d; // "initialization" by reading from input stream
Everything else, responsibility falls on constructors
Therefore, ensure constructors initialize everything in the object
NB: do not confuse assignment with initialization
//01. Assignments
ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
{
// these are all assignments.
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
ABEntry objects with values you expect but not the best approach
Rules of C++ stipulate that data members of an object are initialized BEFORE
the body of a constructor is entered
ABEntry data members are assigned
Initialization took place earlier when default constructor were called automatically
before entering body of ABEntry constructor
Not true for numTimesConsulted, a built-in type
No guarantee it was initialized prior to assignment
Better way to write ABEntry (non-default) constructor
Use member initialization list instead of assignments
//02. Initialization list.
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones) :
theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{
// the ctor body is empty.
}
Results are the same but initialization list is more efficient.
NB: theName(name) invokes copy constructor
But a single call to a copy constructor is more efficient (sometimes MUCH more efficient)
than a call to the default constructor followed by call to copy assignment operator
Built-in types there is no difference but best to initialize EVERYTHING via member initialization
Also, you can use member initialization for default-construct data members;
just specify nothing as an initialization argument
//03. Empty default-constructed values
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones) :
theName(),
theAddress(),
thePhones(),
numTimesConsulted(0)
{
}
Best advice is to do member initialization for all data members
so don’t forget any plus const and reference must be initialized; they can’t be assigned!
AND initialize in the order they’re listed in data member section
because they will be initialized in the data member order i.e.theName initialized before theAddress
LAST THING
The order of initialization of non-local static objects defined in different translation units
STATIC OBJECT
An object that exists from the time it’s constructed until the end of the program
Excludes: stack / heap based objects
Includes: global objects, namespace objects, objects declare static in classes, functions, files
Local static objects are objects declared static in functions
Non-local static objects are everything else
TRANSLATION UNIT
Source code giving rise to a single object file
A single source file plus all #include files
If non-local static object in one translation unit uses a non-local static object in
different translation unit then the object it uses could be uninitialized. Why?
The relative order of initialization of non-local static objects defined in different
translation units is undefined
FileSystem + Directory example
Initialization order is important: FileSystem tfs MUST be initialized BEFORE Directory
But there is no guarantee of this because:
The relative order of initialization of non-local static objects defined in different
translation units is undefined
Why? Because it is impossible to determine the right order of initialization
Solution
Move each non-local static object into its own function where it’s declared static
These functions return references to the objects they contain
Non-local static objects are replaced with local static objects
(Singleton design pattern)
C++ guarantees that local static objects are initialized when the object’s definition
is first encountered during a call to that function
So, if you replace direct access to non-local static objects with calls to functions
that return references to local static objects then you’re guaranteed objects will initialized
Bonus: if you never call function then you never incur const of constructing / destructing obj
class FileSystem { ... }; // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
class Directory { ... }; // as before
Directory::Directory( params ) // as before, except references to tfs are
{ // now to tfs()
std::size_t disks = tfs().numDisks();
}
Directory& tempDir() // this replaces the tempDir object; it
{ // could be static in the Directory class
static Directory td; // define/initialize local static object
return td; // return reference to it
}
Clients of program replace tfs with tfs()
i.e. use functions returning references to objects instead of using objects themselves
IMPORTANT
These functions contain static objects and makes them problematic in multi-threaded systems
non-const static object (local or non-local) may be trouble in multi-threaded systems
Solution
Manually invoke all the reference-returning functions during the single-threaded startup
That eliminates initialization-related race conditions
To avoid using objects before they’re initialized do 3x things:
- manually initialize non-member objects of built-in types
- use member initialization lists to initialize all parts of an object
- design around then initialization order for non-local static objects in diff translation units
REMEMBER
-
Manually initialize objects of built-in type, because C++ only sometimes initializes them
-
In a constructor, prefer member initialization lists to assignment inside constructor body.
List data members in initialization list in the same order they’re declared in the class -
Avoid initialization order problems across translation units by replacing
non-local static objects with local static objects (in functions)