1. Accustoming Yourself to C++
Book review of Effective C++ by Scott Meyers
1. Accustoming yourself to C++
Item 1: View C++ As a Federation of Language
Things to Remember
- Rules for effective C++ programming vary, depending on the part of c++ you are using
Item2: Prefer consts, enums, and inlines to #define
Things to Remember
- For simple constants, prefer const objects or enums to #defines
- For function-like macros, prefer inline functions to #define
- “Prefer the compiler to the preprocessor”
- #define RATIO 3.14 never gets into the symbol table
- So it’s hard to debug because an error message may refer 3.14 not RATIO
- Solution
- const double Ratio = 3.14;
- Ratio is seen by compilers and enters symbol table
- For float, it may take less code because #define result multiple copies of 3.14
- Two special cases when replacing #define
- Constant pointers
- Const to the pointer as well as what pointer points to
-
const char *const name = "Scott"; const std::string name("Scott");
- Class-specific constants
- #define doesn’t have this because it doesn’t respect scope
- static
- to limit the scope and to ensure there’s at most one copy
-
class GamePlayer{ private: static const int NumTurns = 5; int scores[NumTurns]; // array initialized in compile time };
- Usually C++ require you to provide a definition
- Except class-specific constants that are static and of integer type
- if you take their address, put this in an implementation file, not a header file
-
const int GamePlayer::NumTurns;
- because the initial value of class constants is provided where the constant is declared, no initial value is permitted at the point of definition
- Except class-specific constants that are static and of integer type
-
// Use this for the most of times // Goes to header class costEstimate{ private: static const double Fudge; }; // Goes to impl. file const double CostEstimate::Fudge = 1.35;
- Use this except you need value in compile time
- int scores[Fudge]
- “enum hack”
-
class GamePlayer{ private: enum{NumTurns = 5}; int scores[NumTurns]; };
- 1. Behaves more like #define than const
- Legal to take const’s address, but illegal to take enum’s address like #define
- 2. It is purely pragmatic
- you need to recognize it when you see it
- 1. Behaves more like #define than const
- Use this except you need value in compile time
- Constant pointers
- Misuse of #define, use inline (What is inline function?)
-
#define MAX(a,b) f((a)>(b) ? (a):(b)) .. MAX(++a,b); // a is increased twice MAX(++a,b+10); // a is increased once
-
tamplate<typename T> inline void callMax(const T&a, const T&b) { f(a>b ? a:b); }
- Because we don’t know that T is we pass by reference-to-const
- It obeys scope and access rules
- can be private to a class
-
- const, enum, inline reduce need for preprocessor, but it’s not eliminated
- #define RATIO 3.14 never gets into the symbol table
Item 3: Use const whenever possible
Things to 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 as a whole
- Compilers enforce bitwise constness, but you should program using logical 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
- const
- Const allow you to specify a semantic constraint and compilers will enforce that constraint
- (a particular object should not be modified)
- Communicate to both compiler and programmers
- const is remarkably versatile
-
char greeting[] = "Hello"; char *p = greeting // non-const pointer, non-const data const char *p = greeting; // non-const pointer, const data char *const p = greeting // const pointer, non-const data const char* const p = greeting // const pointer, const data
- If const left of *, what’s pointed to is const
- if const right of *, the pointer itself is const
-
void f1(const Widget *pw); void f2(Widget const *pw); // both const Widget object
-
- STL iterators
- Iterator const is like a pointer const
- The iterator isn’t allowed to point to something different
- const_iterator points to something that can’t be modified
-
std::vector<int> vec; const std::vector<int>::iterator iter = vec.begin(); *iter = 10; ++iter; // Error, iter is const std::vector<int>::const_itartor cIter = vec.begin(); *cIter = 10; // Error, *cIter is const ++cIter;
- Iterator const is like a pointer const
- Return const value
- Generally inappropriate, but sometimes can give safety
-
class Rational {...}; const Rational operator*(const Rational &lhs, const Rational &rhs); Rational a,b,c; (a*b) = c; // Prevent this, // invoke operator = on the result of a*b if(a*b = c); // Typo! meant do to ==
- const parameters
- Just acts like local const
- Const allow you to specify a semantic constraint and compilers will enforce that constraint
- const Member Functions
- The purpose is to identify which member functions may be invoked on const objects
- 2 Important Reasons
- 1. They make the interface of a class easier to understand
- Importnat to know which functions may modify an object
- 2. They make it possible to work with const objects
- Critical of writing efficient code
- Improve performance by passing objects by reference-to-const
- Critical of writing efficient code
- 1. They make the interface of a class easier to understand
- Member functions differing only in their constness can be overloaded
-
class Text{ public: const char& operator[](const std::size_t position) const {return text[position];} char& operator[](const std::size_t position) {return text[position];} private: std::sting text; }; /*****************/ Text tb("Hello"); std::cout << tb[0]; // call non-const const Text ctb("World"); std::cout << ctb[0]; // call const (artificial example) void print(const Text& ctb) { // realistic const example std::cout << ctb[0]; }
-
std::cout << tb[0]; // Fine tb[0] = 'x'; // Fine std::cout << ctb[0]; // Fine ctb[0] = 'x'; // Error
- Calling operator[] is fine, but modifying the returned value char& is not fine
- Note operator[] returns reference
- If it returned a simple char, tb[0] = ‘x’; won’t compile
-
- 2 notions of member function to be const
- bitwise correctness (or physical constness)
- It believes a member functions is const if it doesn’t modify any of the object
- Easy to detect violantions
- C++’s definition of constness
- member function isn’t allowed to modify any of non-static data members
- But this is not enough
-
class CText{ public: char& operator[](const std::size pos) const // inappropriate {return pText[pos];} // but bitwise const private: char *pText; }; /**************/ const CText cctb("hello"); char *pc = &cctb[0]; *pc = 'J'; // Inappropriate but no error
- It use C API that doesn’t understand string objects
- -> need of logical constness
- TODO : How logical constness can solve this problem?
-
- Logical constness
- const member function might modify the object, but client can’t detect it
-
class CText{ public: std::size_t length() const; private: char *pText; std::size_t textLen; bool isValid; }; std::size_t CText::length() const { if(!isValid) { testLen = std::strlen(pText); isValid = true; } return textLen; }
- Compilers disagree because it’s not bitwise constness
- Solution : mutable
- mutable frees non-static data members from the bitwise constness constrains
-
class CTextBlock{ ... private: mutable std::size_t textLen; mutable bool isValid; };
- mutable data members may always be modified even in const member functions
- bitwise correctness (or physical constness)
- Avoid Duplication in const and non-const Member Functions
- mutable doesn’t solve all const difficulties
- Having same const and non-const can lead code-bloat
- We want to implement operator[] once and use it twice
- Use casting
- Usually casting is bad idea
- Casting const on the return values is safe because whoever called the non-const operator[] must have had a non-const in first place
- So having non-const operator[] call the const version is safe
-
public Text{ public: const char& operator[](const std::size_t pos) const { return text[pos]; } char& operator[](const std::size_t pos) { return const_cast<char&> ( static_cast<const Text&>(*this)[pos]); } };
- If non-const operator[] it calls recursively itself
- We have to specify we want to call const operator[]
- Cast *this from Text& to const Text&
- We have to specify we want to call const operator[]
- 2 casts
- 1. Add const to *this
- Use static_cast to force a safe conversion
- Why use static_cast not c-like cast?
- 2. Remove the const from the const operator[]’s return value
- Done by const_cast
- 1. Add const to *this
- If non-const operator[] it calls recursively itself
- The other way, const version call the non-cosnt version is not safe
- The object can be changed in non-const version
Item 4: Make sure that objects are initialized before they’re used
Things to Remember
- Manually initialize objects of built-int type
- because C++ only sometimes initializes them itself
- In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor
- Void initialization order problems across translation units by replacing non-local static objects with local static objects
- Reading uninitialized values yields undefined behavior
- In C of C++, no guaranteed of initialization in run time, but in non-C of C++ it’s different
- An array’s contents might not be initialized, but vector is initialized
- Make all constructors initialize everything in the object
-
Entry { private: std::string theName; std::list<Number> thePhones; int numTimes; }; Entry::Entry(..) { theName = name; thePhones = phones; numTimes = 0; } // These are assignments Entry::Entry(...) : theName(name), thePhones(phones), nuTimes(0) {} // These are initializations // Initialize before the body of a constructor is entered
- Initialization is more efficient
- The assignment version
- calls default constructors for theName and the Phones, and assign new values on it
- The default constructor is wasted
- The initialization version
- The arguments are used as constructor arguments
- theName and thePhones are copy-constructed from name and phones
- No difference in built-in type like numTimes
- The assignment version
- Or you can choose to use default constructor like this:
-
Entry::Entry(): theName(), thePhones(), numTimes(0){}
-
- Initialization is more efficient
- Sometimes you must use initialization
- If the data members are const or are references
- Initialization order:
- Base classes first, derived classes second
- How about non-local static objects defined in different translation units?
-
- Static object
- exists from the time it’s constructed until the end of the program
- local static objects
- static objects inside functions
- non-local static objects
- Translation unit
- is the source code giving rise to a single object file
- Basically a single source file with its #include files
- Initialization order of non-static objects defined in different translation units
- The relative order is undefined
-
class FS { public: std::size_t numDisks() const; }; extern FS tfs; // declare object for clients to use // ("tfs" = "the fs"); definition is in some.cpp file /**************/ class Directory{ public: Directory(...); }; Directory::Directory(...) { std::size_t disks = tfs.numDisks(); } Directory tempDir(..);
- You can’t make sure that tfs will be initialized before tempDir
- Solution: Replace non-local static objects with local static objects
- Move non-local static object into its own function where it’s declared static
- The function return reference to the object
- Clients can call the function instead of referencing to the object
- Guarantee that local static objects are initialized when the object’s definition is first encountered
- If you never call the function, never incur cost of constructing the object
-
class FS { FS& tfs() { static FS fs; return fs; } class Directory {...}; Directory::Directory(...) { std::size_t disks = tfs().numDisks(); } Directory& tmpDir() { // this replace tempDir obj // it could be static static Directory td(...); return td; }
- Problem in multi-threading
- Manually invoke all the reference-returning functions in single-thread startup portion
- Move non-local static object into its own function where it’s declared static
- Summary of avoid using objects before using them
- 1. Manually initialize non-member objects of built-in types
- 2. Use member initialization lists
- 3. Design around the initialization order uncertainty that afflicts non-local objects defined in separate translation unit
Item 1: View C++ As a Federation of Language
- 4 sublanguages
- C
- OO C++
- Template C++
- The STL
things to remember
- Rules for effective C++ programming vary, depending on the part of c++ you are using
Item 2: Prefer consts, enums, and inlines to #define
- “Prefer the compiler to the preprocessor”
- #define RATIO 3.14 never gets into the symbol table
- So it’s hard to debug because an error message may refer 3.14 not RATIO
- Solution
- const double Ratio = 3.14;
- Ratio is seen by compilers and enters symbol table
- For float, it may take less code because #define result multiple copies of 3.14
- Two special cases when replacing #define
- Constant pointers
- Const to the pointer as well as what pointer points to
-
const char *const name = "Scott"; const std::string name("Scott");
- Class-specific constants
- #define doesn’t have this because it doesn’t respect scope
- static
- to limit the scope and to ensure there’s at most one copy
-
class GamePlayer{ private: static const int NumTurns = 5; int scores[NumTurns]; // array initialized in compile time };
- Usually C++ require you to provide a definition
- Except class-specific constants that are static and of integer type
- if you take their address, put this in an implementation file, not a header file
-
const int GamePlayer::NumTurns;
- because the initial value of class constants is provided where the constant is declared, no initial value is permitted at the point of definition
- Except class-specific constants that are static and of integer type
-
// Use this for the most of times // Goes to header class costEstimate{ private: static const double Fudge; }; // Goes to impl. file const double CostEstimate::Fudge = 1.35;
- Use this except you need value in compile time
- int scores[Fudge]
- “enum hack”
-
class GamePlayer{ private: enum{NumTurns = 5}; int scores[NumTurns]; };
- 1. Behaves more like #define than const
- Legal to take const’s address, but illegal to take enum’s address like #define
- 2. It is purely pragmatic
- you need to recognize it when you see it
- 1. Behaves more like #define than const
- Use this except you need value in compile time
- Constant pointers
- Misuse of #define, use inline (What is inline function?)
-
#define MAX(a,b) f((a)>(b) ? (a):(b)) .. MAX(++a,b); // a is increased twice MAX(++a,b+10); // a is increased once
-
tamplate<typename T> inline void callMax(const T&a, const T&b) { f(a>b ? a:b); }
- Because we don’t know that T is we pass by reference-to-const
- It obeys scope and access rules
- can be private to a class
-
- const, enum, inline reduce need for preprocessor, but it’s not eliminated
- #define RATIO 3.14 never gets into the symbol table
Things to Remember
- For simple constants, prefer const objects or enums to #defines
- For function-like macros, prefer inline functions to #define
Item3: Use const whenever possible
- const
- Const allow you to specify a semantic constraint and compilers will enforce that constraint
- (a particular object should not be modified)
- Communicate to both compiler and programmers
- const is remarkably versatile
-
char greeting[] = "Hello"; char *p = greeting // non-const pointer, non-const data const char *p = greeting; // non-const pointer, const data char *const p = greeting // const pointer, non-const data const char* const p = greeting // const pointer, const data
- If const left of *, what’s pointed to is const
- if const right of *, the pointer itself is const
-
void f1(const Widget *pw); void f2(Widget const *pw); // both const Widget object
-
- STL iterators
- Iterator const is like a pointer const
- The iterator isn’t allowed to point to something different
- const_iterator points to something that can’t be modified
-
std::vector<int> vec; const std::vector<int>::iterator iter = vec.begin(); *iter = 10; ++iter; // Error, iter is const std::vector<int>::const_itartor cIter = vec.begin(); *cIter = 10; // Error, *cIter is const ++cIter;
- Iterator const is like a pointer const
- Return const value
- Generally inappropriate, but sometimes can give safety
-
class Rational {...}; const Rational operator*(const Rational &lhs, const Rational &rhs); Rational a,b,c; (a*b) = c; // Prevent this, // invoke operator = on the result of a*b if(a*b = c); // Typo! meant do to ==
- const parameters
- Just acts like local const
- Const allow you to specify a semantic constraint and compilers will enforce that constraint
- const Member Functions
- The purpose is to identify which member functions may be invoked on const objects
- 2 Important Reasons
- 1. They make the interface of a class easier to understand
- Importnat to know which functions may modify an object
- 2. They make it possible to work with const objects
- Critical of writing efficient code
- Improve performance by passing objects by reference-to-const
- Critical of writing efficient code
- 1. They make the interface of a class easier to understand
- Member functions differing only in their constness can be overloaded
-
class Text{ public: const char& operator[](const std::size_t position) const {return text[position];} char& operator[](const std::size_t position) {return text[position];} private: std::sting text; }; /*****************/ Text tb("Hello"); std::cout << tb[0]; // call non-const const Text ctb("World"); std::cout << ctb[0]; // call const (artificial example) void print(const Text& ctb) { // realistic const example std::cout << ctb[0]; }
-
std::cout << tb[0]; // Fine tb[0] = 'x'; // Fine std::cout << ctb[0]; // Fine ctb[0] = 'x'; // Error
- Calling operator[] is fine, but modifying the returned value char& is not fine
- Note operator[] returns reference
- If it returned a simple char, tb[0] = ‘x’; won’t compile
-
- 2 notions of member function to be const
- bitwise correctness (or physical constness)
- It believes a member functions is const if it doesn’t modify any of the object
- Easy to detect violantions
- C++’s definition of constness
- member function isn’t allowed to modify any of non-static data members
- But this is not enough
-
class CText{ public: char& operator[](const std::size pos) const // inappropriate {return pText[pos];} // but bitwise const private: char *pText; }; /**************/ const CText cctb("hello"); char *pc = &cctb[0]; *pc = 'J'; // Inappropriate but no error
- It use C API that doesn’t understand string objects
- -> need of logical constness
- TODO : How logical constness can solve this problem?
-
- Logical constness
- const member function might modify the object, but client can’t detect it
-
class CText{ public: std::size_t length() const; private: char *pText; std::size_t textLen; bool isValid; }; std::size_t CText::length() const { if(!isValid) { testLen = std::strlen(pText); isValid = true; } return textLen; }
- Compilers disagree because it’s not bitwise constness
- Solution : mutable
- mutable frees non-static data members from the bitwise constness constrains
-
class CTextBlock{ ... private: mutable std::size_t textLen; mutable bool isValid; };
- mutable data members may always be modified even in const member functions
- bitwise correctness (or physical constness)
- Avoid Duplication in const and non-const Member Functions
- mutable doesn’t solve all const difficulties
- Having same const and non-const can lead code-bloat
- We want to implement operator[] once and use it twice
- Use casting
- Usually casting is bad idea
- Casting const on the return values is safe because whoever called the non-const operator[] must have had a non-const in first place
- So having non-const operator[] call the const version is safe
-
public Text{ public: const char& operator[](const std::size_t pos) const { return text[pos]; } char& operator[](const std::size_t pos) { return const_cast<char&> ( static_cast<const Text&>(*this)[pos]); } };
- If non-const operator[] it calls recursively itself
- We have to specify we want to call const operator[]
- Cast *this from Text& to const Text&
- We have to specify we want to call const operator[]
- 2 casts
- 1. Add const to *this
- Use static_cast to force a safe conversion
- Why use static_cast not c-like cast?
- 2. Remove the const from the const operator[]’s return value
- Done by const_cast
- 1. Add const to *this
- If non-const operator[] it calls recursively itself
- The other way, const version call the non-cosnt version is not safe
- The object can be changed in non-const version
Things to 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 as a whole
- Compilers enforce bitwise constness, but you should program using logical 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
Item 4: Make sure that objects are initialized before they’re used
- Reading uninitialized values yields undefined behavior
- In C of C++, no guaranteed of initialization in run time, but in non-C of C++ it’s different
- An array’s contents might not be initialized, but vector is initialized
- Make all constructors initialize everything in the object
-
Entry { private: std::string theName; std::list<Number> thePhones; int numTimes; }; Entry::Entry(..) { theName = name; thePhones = phones; numTimes = 0; } // These are assignments Entry::Entry(...) : theName(name), thePhones(phones), nuTimes(0) {} // These are initializations // Initialize before the body of a constructor is entered
- Initialization is more efficient
- The assignment version
- calls default constructors for theName and the Phones, and assign new values on it
- The default constructor is wasted
- The initialization version
- The arguments are used as constructor arguments
- theName and thePhones are copy-constructed from name and phones
- No difference in built-in type like numTimes
- The assignment version
- Or you can choose to use default constructor like this:
-
Entry::Entry(): theName(), thePhones(), numTimes(0){}
-
- Initialization is more efficient
- Sometimes you must use initialization
- If the data members are const or are references
- Initialization order:
- Base classes first, derived classes second
- How about non-local static objects defined in different translation units?
-
- Static object
- exists from the time it’s constructed until the end of the program
- local static objects
- static objects inside functions
- non-local static objects
- Translation unit
- is the source code giving rise to a single object file
- Basically a single source file with its #include files
- Initialization order of non-static objects defined in different translation units
- The relative order is undefined
-
class FS { public: std::size_t numDisks() const; }; extern FS tfs; // declare object for clients to use // ("tfs" = "the fs"); definition is in some.cpp file /**************/ class Directory{ public: Directory(...); }; Directory::Directory(...) { std::size_t disks = tfs.numDisks(); } Directory tempDir(..);
- You can’t make sure that tfs will be initialized before tempDir
- Solution: Replace non-local static objects with local static objects
- Move non-local static object into its own function where it’s declared static
- The function return reference to the object
- Clients can call the function instead of referencing to the object
- Guarantee that local static objects are initialized when the object’s definition is first encountered
- If you never call the function, never incur cost of constructing the object
-
class FS { FS& tfs() { static FS fs; return fs; } class Directory {...}; Directory::Directory(...) { std::size_t disks = tfs().numDisks(); } Directory& tmpDir() { // this replace tempDir obj // it could be static static Directory td(...); return td; }
- Problem in multi-threading
- Manually invoke all the reference-returning functions in single-thread startup portion
- Move non-local static object into its own function where it’s declared static
- Summary of avoid using objects before using them
- 1. Manually initialize non-member objects of built-in types
- 2. Use member initialization lists
- 3. Design around the initialization order uncertainty that afflicts non-local objects defined in separate translation units
Things to Remember
- Manually initialize objects of built-int type
- because C++ only sometimes initializes them itself
- In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor
- Void initialization order problems across translation units by replacing non-local static objects with local static objects