Terminology
Parameter: Name of the input in function definition/declaration (exp: x
and y
in void func(int x, double y)
)
Argument: the value passed to a parameter. (exp: 1
and variable a
for function call void func(1,a)
)
Type Conversions
Implicit type conversion through passing function parameter, only occurs if the value is passed by value, or passed by constant reference (i.e. X const &
).
For example passing by non-constant reference (i.e. X &
) will not allow implicit type conversion since if value of the parameter is modified in the function, it cannot be reflected back to argument that is originally used.
Inheritance
Redefinition of base class’s virtual function doesn’t have to have exact same return type as long as returned types are covariant. See here for more details. Exp:
Concurrency
To call a callable only once use std::call_once
. Concurrency safe and thread aware (i.e. even if multiple threads call std::call_once
only one of them will be executed given that flag is shared).
Exp:
if concurrency is not a concern, you can leverage static
variable and lambda functions to achieve the same effect. (I don’t like flag approach generally)
(Smart) Pointers
Do not accept raw/smart pointer parameters to methods if not mandatory. Instead dereference the pointer and pass by reference.
Same statement also holds for smart pointers. Unless the purpose is to share/transfer the ownership, the function doesn’t need to know about the ownership of the object, it just needs to use it.
By accepting unnecessary shared_ptr
s you are making the calling of the function a burden if the caller doesn’t have a shared_ptr
to begin with. (i.e. it has a raw pointer or a reference). This is especially concerning for unit tests.
Use smart pointers if possible and use the right type: unique_ptr
vs shared_ptr
. Entity types should be unique_ptr
unless they are shared among multiple entities.
Value types should prefer shared_ptr
to allow storage in multiple containers and copies when needed.
Do not return raw pointers (or as out parameter), it is hard to establish ownership context without additional metadata. (exp. who will deallocate the memory) There is no (clean) way of knowing what type of memory a pointer is pointing to. It can be containing address of a local variable, global variable, dynamically allocated variable.
Don’t return pointer to local variables.
Need a pointer and reference to a global variable? Prefer defining object as std::unique_ptr
type to begin with and dereference to access the object, instead of defining as object and getting it’s pointer.
C++ guarantees that nullptr
is safe to delete.
Exceptions
You can catch exceptions (like passing function parameters) by value, reference or pointer
Unlike function parameters, object thrown as exception is copied w.r.t its static type (NOT dynamic type, i.e. polymorphic object thrown by reference of its parent type, will be copied as its parent type.)
- This is because unlike function calls, control never returns to where exception is thrown, so the ownership of exception (in case of reference) cannot be maintained by throwing function.
Rethrowing:
- Rethrowing an exception by
throw e
will causee
to be copied.- Object is copied as the caught type.
- Rethrowing an exception by
throw
will only propagate captured exception without any new copy.- Preserves caught objects dynamic type.
Possible combinations and their ramifications:
- Object thrown and caught by value. Object is copied twice, one due to throw rule mentioned above and second is due to capturing it by value.
- Object thrown and caught by reference. Object is copied once.
- Pointer thrown and caught by pointer. Pointer copied.
- Throwing pointer is the only way of not copying exception object.
Exceptions will trigger stack unwinding, i.e. destructors of your local variables will be called.
- Use smart pointers instead of raw pointers to avoid memory leaking due to exceptions.
A partially constructed object, (one that may thrown an exception in the middle) will never have its destructor called.
- C++ destructors are only called for fully constructed objects.
- In constructor exceptions of called methods (including
new
) should be caught and handled.
If an exception leaves the destructor while another exception is active (i.e. destructor is called due to another exception). C++ will call terminate
.
- You can still catch exceptions in destructor preventing them leaving it.
catch
clause only catches the specified type with two exception conversions:
- It will accept inheritance imposed conversions (a derived object can be caught by parent type)
- It will accept typed to untyped pointer conversions (any exception pointer can be caught by
void *
type)
catch
clauses will be tried in order of their appearance in the code. Catch order should be aware of inheritance among types (derived type should be caught earlier than parent type)
C++ standard exceptions are never thrown as pointers.
Most ideal way of handling exceptions is by catching them as references and rethrowing them by throw
(as oppoes to throw e
).
Unhandled exceptions at runtime cause invocations of unexpected
, and unexpected
method by default call terminate
which by default call abort
.
- Default behaviour ends up causing no proper stack unwinding to occur
You can update the unexpected
handler with your custom method to convert it into a known exception to prevent terminate
getting called. (see set_unexpedted
)
Object Oriented Programming
- Motivation: Declaration of a class should only contain the information relevant to class user, not implementer.
- Define private methods in class implementation file, instead of defining them part of the class.
- Most (see below) private methods don’t communicate anything useful to class user.
- Creating unit tests for private methods it too troublesome.
X::IncrementA
method would except all parameters,and/or be static, still due to it being private would prevent us from creating a unit test. private
meethods are there to communicate what functionality that is expected to be generally available, is forbidden.- exp: Hidden constructor, copy-constructor, assignment operator etc.
- If a class is handling multiple tasks, break each task into its own class, i.e. decomposition. Decomposition helps with reducing coupling and incentivizes good interface.