C vs. C++ Functionality
C++ is a superset of C, but some C functionality has been superceded and should not typically be used in C++ code. One exception to this rule is when interacting with C code, sometimes we are forced to use C functionality.
free should never be used. The
delete operators should be used instead. These are type-safe, and they also ensure that the appropriate constructor and destructor is called.
Header files (e.g.
.hpp) typically contain function declarations, while source files (e.g.
.cc) typically contain function definitions.
Source files can
include header files in order to gain access to the declarations therein. After compilation, it is the responsibility of linker to supply the appropriate definition for each referenced declaration; a header can tell the compiler that a function exists, but the compiler does not know where it is defined.
In theory everything could be defined in header files but this would drastically increase the compile time.
Each symbol (function, variable, etc.) can only be defined once. Otherwise how can the linker know which version is the correct one?
This is problematic when definitions are present in header files, as the same header files may be included multiple times within a program.
The solution for this is to use include guards:
#ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H // header file contents #endif
The practice of relying on constructor and destructors for memory management is sometimes referred to as Resource Acquisition Is Initialization (RAII).
Memory leaks are still possible if we forget to call
delete. One solution to this is to use smart pointers, which call
delete automatically based on reference counting.
Heap vs. Stack
new operator creates an object on the heap, but by default objects are created on the stack. Stack-allocated objects are automatically deleted (and their destructor called) when they go out of scope, making them much easier and safer to use.
Stack-allocated objects are also cheaper to create and access, since their memory address can be determined at compile time. However, the stack size is very limited compared to the heap, so it should not be used for large objects.
Basic Syntax & Keywords
The right-left rule allows us to reliably interpret complex type declarations. It is defined as follows:
- Start reading the declaration from the identifier.
- Read to the right until a closing bracket is reached.
- Read to the left until the matching bracket is found.
- Repeat from step 2, starting outside the brackets, until the whole declaration has been parsed.
int * (* (*fp1) (int) ) ;
Start from the variable name: -> fp1 Read right, hit a bracket; go left to find * -> is a pointer Read right again outside the brackets; find (int) -> to a function that takes an int Hit another bracket; go left to find * -> and returns a pointer Read right again outside the brackets; find  -> to an array of 10 Read left to find * -> pointers to Keep reading left to find int -> ints
Variables, parameters and methods should be denoted as
const wherever possible to prevent accidental modification.
const methods of a class can only call other
This is generally appropriate for getter methods (unless they return a reference to a member variable, because that would enable modification of that object).
East / west
There is ongoing debate about whether the
const keyword should be placed to the left or to the right of a variable declaration. The former is arguably more natural, but the latter is more consistent.
constexpr should be used for variables and functions whose values can be determined at compile time. These can then be used in the declarations of
Functions that should never throw an exception can be marked
noexcept. This is a useful form of documentation, and it can allow certain optimisations by the compiler. Should such a function throw an exception, the program will terminate.
inline is rarely needed, as the compiler generally knows when this kind of optimisation is appropriate. However, small functions defined in header files are good candidates for
inline tells the compiler not to produce an error if a function definition is encountered multiple times, on the assumption that the function is defined in every translation unit where it is used, and that each definition is exactly the same.
Pointers & References
It is better to place the
&) next to the variable name, to prevent misleading declarations like this:
int* p, q;
In this example, only
p is actually a pointer.
This ambiguity can be resolved with the help of a
typedef. In this example, both
q are pointers:
typedef int * IntPointer; IntPointer p, q;
Pointers to Arrays
int tiles; int (*p) = tiles; // p is a pointer to an array of 4 ints; // (`tiles` is equivalent to `tiles`) int *q; // q is a pointer to an array of 5 ints
Function pointers are declared using:
For example, here
p is a pointer to a function that takes 2
floats and returns a pointer to a
char * (*p)(float, float);
->) vs. Dot (
The arrow is used when accessing an object's members, when we have a pointer to the object.
This dereferences the pointer before accessing the member. Dereferencing a pointer means getting the value that is stored in the memory location that the pointer points to.
In other words,
foo->bar() is the same as
Rule of Five
The following resources were used in the creation of this article: