Reference: Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Prefer consts, enums, and inlines to #defines
Prefer the compiler to the preprocessor because #define may be treated as if it is not part of the language per se.
1 |
|
When replacing #define with constants be aware:
Defining constant pointers: Important that the ptr to be constant
- const char * const varName = “value”;
- varName is a const ptr that points const char which is “value”
- For string: const std::string varName(“value”);
Class-specific constants: ensure only one copy of it -> static like following
- Usually CPP requires definition except class-specific constants of integeral type (int, chars, bools…).
1
2
3
4
5
6
7
8class Temp{
private:
static const int var = value;
// constant declaration + definition
// It may be illegal for older compilers, as was illegal to provide initial value for static member. If that,
static const int old;
};
const int Temp::old = 1; // def goes in impl. file
- Usually CPP requires definition except class-specific constants of integeral type (int, chars, bools…).
If, somehow, statis must have value during compilation, use enum hack
- Somehow behaves more #define than a const
- It’s legal to take address of const, but enum is not.
- Typically, not legal to take address of #define
- So, if you don’t want user get a ptr or reference of your constants, use enum
- Enum hack is a fundamental technique of template metaprogramming that you can know when you see it what is it for.Don’t use #define macro like MAX_IS(a,b) f((a…)), but use inline type template instead.
1
2
3
4class Temp{
private:
enum {VarName = 5}; // VarName a symbolic name for 5
};1
2
3
4
5
6
7
// Instead use
template <typename T>
inline void callWithMax(const T& a, const T& b){
f(a > b ? a : b);
}
// Can have efficiency of macro (less overhead for calling a function) + predictable behaviour + type safety
Use const whenever possible
It allows you to specify a semantic constraint -> particular object not be modified
1 | char greeting[] = "HI"; |
Declaring an iterator const = declaring a const ptr
Check the following:
1 | // itr = T * const |
1 | const Temp operator*(const Temp& lhs, const Temp& rhs); |
To remember:
- Declaring const -> helps compilers to detect usage errors.
- Compilers enforce bitwise constness, but you should program using conceptual constness.
- if const and non-const have same implementation, have non-cost and call const version.
Make sure to initalise objects before use
Very complicated to understand which one initialised or not -> so always initialise the objects. Dont get confused with assignment and initialisation.
1 | Temp::Temp(a, b, c){ |
Order:
- Base class is initialised before derived class.
- Data member is initialised in the order of its declaration.
The order of initialisation of non-local static objects defined in different translation units.
- A static object:
- one that exists from the time it’s constructed until the end of the program (on heap).
- so stack and heap-based objects are thus excluded (as it can be deallocated before the end of the program)
- So include global objects, objects defined at namespace scope, objects declared static inside classes, and inside functions and objects declared static at file scope.
- Static objects inside functions: local static objects
- Others are: non-local static objects.
- one that exists from the time it’s constructed until the end of the program (on heap).
- A translation unit: source code giving rise to a single object file - a basic unit of cpp compilation (source file + files included as #include).
It becomes an issue when it involves at least two separately compiled source files: Each contains non-local static object with dependency because the relative order of initialization of non-local static objects defined in different translation units is undefined
Problem solved entirely by set both non-local static object to be local static objects -> singleton pattern.
- CPP gurantee that local static objects are initialised when object’s definition is encountered when to call it.
- By changing direct access to non-local static object to calls to functions (that returns local static..), problem gets solved.
1 | // Example. |
This reference-returning function is simple: define and initialise a local static object, and return.
- It makes excellent candidates for inlining if called frequently.
- It makes problematic in multithreaded systems; but it is just what non-const static object always problematic in any multithreaded systems.
- To solve this, have to manually invoke the functions in single-thread to avoid initialisation-related race condition.
Summary
To avoid initialisation problems -> remember three:
- Manually initialise non-member objects of built-in types.
- Use member initialisation lists to initialise all parts of an object (In a constructor).
- Design to avoid that non-local static object defined in separate translation units problem.