C++ topics by example
virtual destructor
Ch7 Functions
Ch8 Basic OO
- properties: class/object(data+methods),
inheritance,
encapsulation(getter and setter),
abstraction,
polymorphism
- struct: public by default; class: private by default
- protected:
constructor, copy constrcutor, assignment operator, shallow vs deep copying
(constructor1.cpp,
constructor2.cpp,
shallow_deep_copy.cpp,
)
- why copy/assignment constructor: assignment operator may not work as expected; dealing with dynamically allocated memory
- assignment operator: copy from one object to another *already existing* object
- copy operator: initialize a *new* object from an existing object
- copy operator: passing an object by value, return from a function by value
- const object can only call const const member function
(const.cpp)
- const member function guarantees that it will not change any class variables or call any non-const member function
Ch10 Composition
constrcutor init list
composition
(main.cpp,
Creature.h,
Point2D.h)
aggregation
container class
(main.cpp,
IntArray.h,
IntArray.cpp)
const member functions
I/O overloading (friend functions)
Ch11 Inheritance
- virtual base classes: means there is only one base object that is shared; virtual base class are created before non-virtual base classes; and the most derived one is responsible for the virtual base class constructor
Ch12 Virtual Functions
- dynamically determin output for this format: Base *b = new Derived() virtualfun.cpp
- whenever you are dealing with inheritance, you should make your destructors virtual.
- why not all functions virtual: 1) resolving a virtual function call takes longer than a resolving a regular one; 2) the compiler also has to allocate an extra pointer for each class object that has one or more virtual functions.
- virtual table: 1) First, every class that uses virtual functions (or is derived from a class that uses virtual functions) is given it.s own virtual table. This table is simply a static array that the compiler sets up at compile time. A virtual table contains one entry for each virtual function that can be called by objects of the class. Each entry in this table is simply a function pointer that points to the most-derived function accessible by that class. 2) the compiler also adds a hidden pointer to the base class, which we will call *__vptr.
- pure virtual functions: A pure virtual function simply acts as a placeholder that is meant to be redefined by derived classes. 1) any class with one or more pure virtual functions becomes an abstract base class, which means that it can not be instantiated; 2) any derived class must define a body for this function, or that derived class will be considered an abstract base class as well.
- interface class: An interface class is a class that has no members variables, and where all of the functions are pure virtual. Interfaces are useful when you want to define the functionality that derived classes must implement, but leave the details of how the derived class implements that functionality entirely up to the derived class. Interface classes are often named beginning with an I.
Ch13 I/O
Ch15 Exceptions
throw, try, and catch
stack unwinding
int DoSomething() throw(); // does not throw exceptions
int DoSomething() throw(double); // may throw a double
int DoSomething() throw(...); // may throw anything
Note that exception handlers should catch class exception objects by reference instead of by value. This prevents the compiler from make a copy of the exception, which can be expensive when the exception is a class object. Catching exceptions by pointer should generally be avoided unless you have a specific reason to do so.
Rule: Handlers for derived exception classes should be listed before those for base classes.
Let.s take a look at this using std::exception. There are many classes derived from std::exception, such as std::bad_alloc, std::bad_cast, std::runtime_error, and others. When the standard library has an error, it can throw a derived exception correlating to the appropriate specific problem it has encountered.
std::auto_ptr is a template class that holds a pointer, and deallocates it when it goes out of scope.
Note that std::auto_ptr should not be set to point to arrays. This is because it uses the delete operator to clean up, not the delete[] operator.
In fact, there is no array version of std::auto_ptr! It turns out, there isn.t really a need for one. In the standard library, if you want to do dynamically allocated arrays, you.re supposed to use the std::vector class, which will deallocate itself when it goes out of scope.
Unlike constructors, where throwing exceptions can be a useful way to indicate that object creation succeeded, exceptions should not be thrown in destructors.
Ch16 STL
- STL: containers, algorithms, and iterators
- containers: sequence container, associative container, and container adapters
- sequence container: vector, deque, list, string(wstring) - char(wchar)
- associative container: set, multiset, map, multimap
- container adapter: stack, queue, priority queue
Sequence containers are container classes that maintain the insert ordering of elements.
- vector: elements can be randomly accessed via operator[], it's faster to insert/remove elements from the end of the vector
- deque(read as "deck"): double ended, can grow from both ends, push_back(), push_front()
- list: doubly linked list where each element in the container contains pointers that point at the next and previous elements in the list. Lists only provide access to the start and end of the list - there is no random access provided. If you want to find a value in the middle, you have to start at one end and walk the list until you reach the element you want to find. The advantage of lists is that inserting elements into a list is very fast if you already know where you want to insert them. Generally iterators are used to walk through the list.
Associative containers are containers that automatically sort their inputs when those inputs are inserted into the container. By default, associative containers compare elements using operator<.
- set: stores unique elements according to their values
- multiset: a set where duplicate elements are allowed.
- map: a set where each element is a pair, called a key/value pair. The key is used for sorting and indexing the data, and must be unique. The value is the actual data.
- multimap: a map that allows duplicate keys
Container adapters are special predefined containers that are adapted to specific uses. The interesting part about container adapters is that you can choose which sequence container you want them to use.
- stack: LIFO (Last In, First Out) context, elements are inserted (pushed) and removed (popped) from the end of the container, Stacks default to using deque as their default sequence container, but can use vector or list as well
- queue: FIFO (First In, First Out) context, elements are inserted (pushed) to the back of the container and removed (popped) from the front. Queues default to using deque, but can also use list.
- priority queue: a type of queue where the elements are kept sorted (via operator<), When elements are pushed, the element is sorted in the queue. Removing an element from the front returns the highest priority item in the priority queue.
tips
- container::iterator, and container::const_iterator
- when inserting, sequence container use push_back(), and associate container use insert()
STL algorithms
- operations: search, sort, insert, reorder, remove, and copy
- it will generally automatically work for all containers that provides a set of iterators (including your custom container classes)
c++11 features
- containers: array, forward_list, unordered_set, unordered_multiset, unordered_map, unordered_multimap
- regular expression library
- atomic operation library
- thread support library
Apendix A
mutable: to handle the special cases where an object is logically constant, but for implementation reasons need to change. Eg, cash the expensive result
smart pointer: As you can see, auto_ptr is a simple wrapper around a regular pointer. It forwards all meaningful operations to this pointer (dereferencing and indirection). its smartness in the destructor: the destructor takes care of deleting the pointer. If we use a smart pointer, however, p will be cleaned up whenever it gets out of scope, whether it was during the normal path of execution or during the stack unwinding caused by throwing an exception.
check if the pointer is null (if(ptr)) before dereference
cast: static_cast, const_cast, reinterpret_cast, dynamic_cast
Appendix B
The code area, where the compiled program sits in memory.
The globals area, where global variables are stored.
The heap, where dynamically allocated variables are allocated from.
The stack, where parameters and local variables are allocated from.
The heap has advantages and disadvantages:
1) Allocated memory stays allocated until it is specifically deallocated (beware memory leaks).
2) Dynamically allocated memory must be accessed through a pointer.
3) Because the heap is a big pool of memory, large arrays, structures, or classes should be allocated here.
The call stack is a fixed-size chunk of sequential memory addresses.
The stack pointer keeps track of where the top of the stack currently is.
So what do we push onto our call stack? Parameters, local variables, and. function calls.
Because parameters and local variables essentially belong to a function, we really only need to consider what happens on the stack when we call a function. Here is the sequence of steps that takes place when a function is called:
The address of the instruction beyond the function call is pushed onto the stack. This is how the CPU remembers where to go after the function returns.
Room is made on the stack for the function.s return type. This is just a placeholder for now.
The CPU jumps to the function.s code.
The current top of the stack is held in a special pointer called the stack frame. Everything added to the stack after this point is considered .local. to the function.
All function arguments are placed on the stack.
The instructions inside of the function begin executing.
Local variables are pushed onto the stack as they are defined.
When the function terminates, the following steps happen:
The function's return value is copied into the placeholder that was put on the stack for this purpose.
Everything after the stack frame pointer is popped off. This destroys all local variables and arguments.
The return value is popped off the stack and is assigned as the value of the function. If the value of the function isn't assigned to anything, no assignment takes place, and the value is lost.
The address of the next instruction to execute is popped off the stack, and the CPU resumes execution at that instruction.
Typically, it is not important to know all the details about how the call stack works. However, understanding that functions are effectively pushed on the stack when they are called and popped off when they return gives you the fundamentals needed to understand recursion, as well as some other concepts that are useful when debugging.
The stack has advantages and disadvantages:
Memory allocated on the stack stays in scope as long as it is on the stack. It is destroyed when it is popped off the stack.
All memory allocated on the stack is known at compile time. Consequently, this memory can be accessed directly through a variable.
Because the stack is relatively small, it is generally not a good idea to do anything that eats up lots of stack space. This includes allocating large arrays, structures, and classes, as well as heavy recursion.
new/delete:
'malloc()' allocates a bunch of bytes while 'new' does the allocation as well as has the responsibility of calling the constructor.
'malloc' returns 'NULL' on failure while 'new' throws an exception object of type 'std::bad_alloc'. (There exists a nothrow version of 'new', though)
'malloc' is the C way of dealing with the free store while 'new' is the C++ way.
Every call to 'malloc()' should be followed by a call to 'free()' while every call to 'new' should be followed by a call to 'delete'.
'free()' deallocates all bytes allocated by a prior 'malloc()' call (using the same pointer) while 'delete' makes a call to the destructor and then goes on with the deallocation.
There are array forms of 'new' and 'delete' while 'malloc()' and 'free()' do not (as the request is for a specific size of raw memory in bytes).
'free()' on 'NULL' pointers is not safe while 'delete' is.
data allignment and data padding