##Effective C++ Notes
### Part I. Accustoming Yourself to C++
####1. View C++ as a federation of languages
- C++ is a multi-paradigm programming language with paradigms including procedure oriented, object oriented and generic programming with their own rules. Which leads to the complexity of such language.
####2 Perfer consts, enums and inlines to \#defines.
- \#define is not counted as a part of the language itself, thus anything defined by \#define is not visible to the compiler, things defined by \#define won't be in the symbol table, in another way. Which will lead to difficulties on debugging and refactoring. The better way is to take over \#define by const decoration.
- definition of non-internal type const class member is not allowed in class declaration.
~~~c++
class A
{
private:
static const int member = 1; //OK
static const string str = "Hello"; //Not available
}
const A::string str = "Hello"; //Real definition in .cc file
~~~
- enum hack to achieve const static class members
~~~c++
class A{
private:
enum { cnt = 10 };
int People[cnt];
}
~~~
- pitfalls of \#define
- preprocessor don't do type check, which means misused macros may lead to compile time error
- if parameters of a macro is not protected by appropriate brackets, operator priority may leads to unpredicted result
~~~c++
#define mul(a,b) a*b
~~~
mul(1+2, 3+4) will be reaplace by 1+2*3+4 which is clearly what we want.
- Even correctly protected macro parameters may crash
~~~c++
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
~~~
define a = 5, then CALL\_WITH\_MAX(++a,0) will perform f(++a) thus a is incremented twice, but CALL\_WITH\_MAX(++a, 10) will perform f(10) thus a is incremeted once. It is NOT acceptable that A macro's behavior is realted to its parameters which may lead to misreable effects
- To avoid these pitfalls we can use template inline functions which is as efficiency as macros but much more safer.
####3. Use const, enum and inline instead of #define, Use const whenever possible
- const decoration behaves based on some complex rules espacially when combined with pointers
~~~c
const char* str = "Hello World"; //const string, non-const pointer
char const* str = "Hello World"; //const string, non-const pointer
char* const str = "Hello World"; //const pointer, non-const string
const char* const = "Hello World"; //const pointer, const string
~~~
- Returning a const may reduce the possibility that the client mess things up via the compiler, a compile time error ins always better than a runtime error
~~~c++
class A;
const A& operator*(const A& lhs, const A& rhs) {...}
A a, b;
a * b == a; //OK
a * b = a; //Compile time ERROR, const instance can't be changed
~~~
- const member functions indicates that the function won't change the class members **BITWISELY**, thus the function can be performed on a const object
- if a class need a const member function and a non-const overload of such function, the non-const version should be implemented by calling the const version and then perform the const cast on it in order to avoid duplicated codes with garuantee on const properties
~~~c++
class Text
{
public:
const char& operator[](size_t pos)
{ return text_[pos]; }
char& operator[](size_t pos)
{
return const_cast<char>(c
static_cast<const Text&>(*this)[pos]);
}
private:
string text_;
};
~~~
the const version garantees no changes on the object, so it is safe to implement the non-const version function via it, nevertheless it's not appropriate to do it in a reversed way since non-const function doesn't has such garantees
- bitwise constness is garanteed by the compiler but what we need is conceptual constness indeed, mutable declaration will do that
####4. Make sure that objects are initialized before they're used
- whether a variable is initialized by default is not clear in C++, which may lead to unspecified and unpredictable behavior. The rule of default initialization is very complex.
- For the internal types, the C part and non-C part of C++ follow different rules of initialization which resulted in the complexity, thus, it is a good approach to manually initialize a variable before using it in order to guarantee a correct initialization.
- For user-defined types, constructors should provide such guarantee, **Ensure every class member is initialized via every single constructor**.
- Assignment and initialization are different concept in constructor
~~~c++
class Contact
{
public:
Contact(string name, int age, string number)
: name_(name), age_(age), phoneNum_(number) {} //This is initialization
Contact(string name, int age, string number) //This is assignment
{
name_ = name;
age_ = age;
phoneNum = number;
}
private:
std::string name_;
int age_;
std::string phoneNum_;
}
~~~
initialize list is more efficiency than assignments since member instances are initialized on construction, in this case, copy constructor of member variable name_, age_ and phoneNum_ are called before entering the body of contact constructor. In assignment method, default constructor of member variables are called and later assigned with a new value in the body of contact constructor. But in some cases where a class contains several contructors and there are some internal types as member variables, a pesudo-initialization maybe a better solution in consideration of avoidance of duplicating codes.
- initializing sequence of non-local static object defined in different compile units
static objects keep alive since been constructed to the exit of a program, including global objects, objects defined in namespace scope, objects defined in classes and in file scope. non-local static variables are constructed before entering main(). Thus, sequence of initializing of non-local static variables in different compile units will be unsettled.
**File1:**
~~~c++
//file1
class FileSystem
{
public:
std::size_t numDisks() const;
};
extern FileSystem tfs;
~~~
**File2:**
~~~c++
class Directory
{
public:
Directory(param);
}
Directory::Directory(param)
{
std::size_t disks = tfs.numDisks();
}
~~~
**Somewhere in customer code:**
~~~c++
Directory tmpDir(param);
~~~
No one knows when object tfs is initialized. ISO C++ doesn't regulates the timepoint when object is initialized, but different non-local static objects may have dependency which may generate a compile time error. Fortunately, this problem could be solved by a the singleton pattern, by replace the non-local static objects with functions returning a reference to a local static object.
**Revised:**
~~~c++
class FileSystem {...};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {...};
Directory tmpDir()
{
static Directory td;
return td;
}
~~~
IMPORTANT: this simple singleton implementation is NOT thread-safe.
#### Conclusions:
1.1 Rules for effective C++ programming vary, depending on the part of C++ you are using
2.1 For simple constants, prefer const objects or enums to #defines
2.2 For function-like macros, prefer inline functions to #defines
3.1 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 function as a whole
3.2 Compilers enforce bitwise constness, but you should program using logical constness.
3.3 When const and non-const member functions have essentially identical implementations, code deplication can be avoided by having the non-const version call the const version
4.1 Manually initialize objects of built-in type, because C++ only sometimes initializes them itself
4.2 In a constructor, prefer use of the member initialization list to assignment inside the body of constructor. List data members in the initialization list in the same order they're declared in the class
4.3 Avoid initialization order problems across translation units by replacing non-local static objects with local static objects