Foreword ======== Effective C++ is a book telling you about various traps in C++ that certain ways of coding is legal in C++, but it's not commonly done for a good reason. Stick with the common conventions as the first choice. Do some serious research before doing something inventative! I'll skip - hard rules: like matching new[] and delete[] and not returning stack handles - style guidelines: comment, display output, namespaces, avoid macros, ... It's the places where the programmer has to make difficult judgments that I want to comment on. Slangs ====== behavior: methods (of certain signature) handle: pointer AND/OR references functor: function handles Rationale from C ================ [50] C++ originated as an extension to C, which the toolchain is characteristically defined by - Preprocessor - Compiler - Linker It's not hard to imagine how the language features are built from the basic IDEAS around these 3 main translation units. A) Preprocessor 1) inline functions: basically copy-and-paste a chunk of code (technically at machine code level, but put here for concept) (this also applies to C99 too, so nothing OOP about it) [33] Implications if the inline is successful: a) No function is generated i) No function handles. b) Code chunk copied everywhere i) Code bloat - Too much code bloat requires long jumps, slowing the code down 2) Templates (technically at compilation level, but here for concept) B) Compiler 1) [21] const: aid compiler to check your work syntatically a) const method: bytewise const i) the const-method overloaded version will be invoked for const objects ii) [29] can be circumvented (e.g. const_cast) if handles to it are exposed iii) [30] use mutable to make exceptions to const methods (mostly used in mutexes) 2) Access control (public/protected/private) are as enforced as consts (syntactic, not bulletproof) a) Possible to circumvent i) Handles (so try not to expose private handles) b) [26] access level should not change meaning of a program -> access level not taken into account to resolve ambiguity C) Linker 1) Access control emulated by thinking of a class as a file (global static functions/variable as private) Conceptually C++ defined a compilation module to be per class/namespace instead of a file. Language features with roots from C =================================== A) Classes Originally supposed to emulate a plain old data (POD) type like int. 1) Operator overloading a) Follow same behaviors as POD. i) [15] allow (a=b)=c in operator=(): chaining (return *this) ii) [21] disallow (a+b)=c in operator+(): rvalue assignment (return const object) Basically an extension of struct 2) Methods: function pointers /w signatures kept track of by name mangling 4) this pointer: pointer to struct a) [17] Check for self-assignment in operator= using *this B) Handles: Pointers & References 1) Reference is an alias (nickname) to an object. 2) References are semi-conceptually: ref <=> * const ptr a) pointer is const protected, so references are i) bound on creation, ii) cannot be reassigned b) Has all the power and responsibility of a pointer i) Underlying object might expire - Stack objects gets unrolled - Implicitly created temporary objects - Heap allocations freed C) Binding In C, pretty much everything is statically bound at compile time. By default C++ recognize objects (handles) by their type *declaration* (compile-time/statically-linked), NOT their underlying content. -> the handle's type determines its behavior for NON-VIRTUALS. With a bit of overhead, C++ can figure out the underlying object type on-the-fly. virtual', RTTI and uses dynamic binding. For efficiency, C++ chose to statically bind default parameters. There are no mechanism like vtable to decide whether to load the default parameters from parent or child (default parameters not virtualized). -> virtual (see below) applies to the method, not default parameters -> using virtual with different child parameters will end up having calling the child method load with parent parameters (for parent handles) -> Almost always never intended, so it's a trap. [38] Do NOT put in different default parameters for child D) Miscellaneous [25] C doesn't distinguish between pointers and integers -> Signatures might be ambiguous when you overload both kinds OOP === [35] Public inheritance <=> isa(): mandatorily inherits behavior (unless virtuals) [40] Prefer composition <=> has-a, or implemented in terms of [42] Private inheritance <=> implemented in terms of (the parent is usually protected to prevent other uses) [36] Non-virtual public inheritance: MANDATORILY inherit implementation along with interface -> Implies invariance through specialization (deriving) -> Usually bad idea to hide it in derived class [37] because it contradicts the intention to be invariant. [36] Pure virtuals: inherit interface Only -> Implementation, if exist, can ONLY be called with SRO to base class -> Can be neatly used as a shared protected method to be called by child [43] virtuals: inherit interface, along with DEFAULT implementation [43] virtual inheritance: solves diamond problem by having a separate method for each parent belonging to the same grandparent. C++ features that are not obvious from the C mentality ====================================================== A) Object code section specifically for C++ 1) Constructors (.ctor) & Destructors (.dtor) There is a reserved .ctor and .dtor section for jumping to at entry/exit Destructors: In C, it's done by "goto finishLabel;" 2) .vtable: a mechanism added in C++ to allow child objects to track themselves despite being type referenced by their parent handle. In other words, this is dynamic (runtime) object binding as the compiler won't know what kind of child objects that the parent handle(s) actually contains. If you find yourself explicitly writing if/else/switch that requires knowing the type to conditionally run code, good chance it can be done by inheriting the interface using virtual keyword. It's better OOP design to have your code figure out what to do by context instead of micromanaging it. virtuals are needed ONLY if you refer to child objects by using its parent HANDLES (e.g. a collection of heterogenous objects) and want them to behave (by methods) like the underlying child. -> Classes intended to be final (no child) don't need virtuals. Whether virtuals are enabled are dictated by the parent (propagated down) -> the virtual keyword is optional for child methods, though good practice. Inner workings: each class in the hierachy has a (v)table keeping track of what methods (i.e. the most derived) a signature can call. -> Each class in the hierachy adds a hidden *_vptr that points to the vtable -> Extra memory -> Object size different from (larger than) struct -> Cannot assume memory alignment like struct -> Extra jumps [14] Undefined behavior when *deleting* child through parent handle with *non-virtual* destructor. Destructors should be either or disabled: B) Default methods 1) [45] Default assignment constructors: bytewise copy like copy constructor -> Refuse to copy const members -> Refuse to copy references (cannot be reassigned because it's like a *const ptr) 2) [45] Address-of operator&() is one of them if not specified! --------------------------------- Beginners --------- [11] Write a copy constructor if there's heap allocation [12] Use initializer list in constructor (NOT the {}) - Have to use it for consts (and references, since references are *const ptr) - Efficiency: skip calling member's default constructor then modify them. [13] Initialization list executes in the order of declaration, not how you type it -> So type it the same way as it's supposed to execute! [11, 16, 27] Default destructors, constructors, copy constructor and operator=() (collectively called "Special Member Functions", C++11 includes move constructor as well) not created once you mention them (by declaration. Doesn't matter whether you implement it). -> [27] Can disable them by just declaring it private/protected without implementation. [20] Use get/set wrapper (for future read/write control) instead of public members [24] Use common underlying function for constructors with many different input variations B) RAII: Let the constructors and destructor handle allocation and cleanup. Needed technique for exception-enabled C++ Advanced ======== A) Operator overloading 1) Nonmember operators() a) binary (+,-,*): want implicit type conversion on first input (first input is expected to be the object itself if the operator is a member) make them friends if you need to use 'this' pointer 2) [23] Some operators must return objects instead of handles a) binary operators: often don't want to modify either inputs -> Make a new object to store results regardless. -> Trick: can pass one by value, modify then return object. 3) Read/Write operator[] and operator(): return references to positions for read/edit Write: S& operator[](...) Read: const S& operator[](...) const The const method is to distinguish the signatures because C doesn't differentiate return types B) Design patterns [34] Members defined in header forces recompiling when object changes -> Slow compilation -> Proxy/Factory design pattern to decouple members by calling them with wrapper pointers [Wrapper pointer: abstract class, Underlying object derive from it] [47] Use singleton (relies on a plain function returning reference to a STATIC object declared within the function) to guarantee initialization order of non-local static objects (global/namespace/file-local/class-static)