These are personal notes as I’ve decided to open a C++ 98 (ISO14882) reference book.
Operators
These operators can be overloaded in a class operator*(int a, int b){…}
delete delete[] new new[] xor bitand bitor compl not xor_eq and_eq or_eq not_eq and or
+ – * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ — , ->* -> () []
Language reference
- and a&&b (logical) or a||b xor a^b compl ~a not !a returns true/false
- and_eq a&=b (bitwise) or_eq a|=b xor_eq a^=b performs the operation a = a&b
- eq a==b not_eq a!=b returns true/false
- asm asm(“…”);
- auto int x =2; auto y = x;
- bitand a&b (bitwise) bitor a|b not !b
- bool, true, false boolean values bool b = true;
- break do/for/while/switch
- char, wchar_t size is always 1 and stores an ASCII character symbol. wchar_t stores a UTF8 character char c = ‘\n’; sizeof(char) ==1;
- cast a cast is can be implicitly done by changing the type as is done in C: uint32_t u; int32_t i = (int32_t) u; . However C++ has explicit cast operators.
- dynamic_cast<>() : polymorphic pointer cast between base / derived classes. It allows casting a base class to one of the derived classes, for example you can check is a shape object is a round object then cast it to round before passing it to methods that only accept round objects.
class b{};
class d: b {};
b* bb = new d; b* bb = dynamic_cast<d*>(bb); - reinterpret_cast<>() allows an unsafe cast between types. Be mindful you can only cast to a stricter type alignment. You can only cast a function pointer to another function pointer. Pointer to class members can only be cast to other class members (because of *this). You can otherwise cast between pointers and integers and between any built-in types. Casting a null pointer just changes the type of the null pointer.
char * pc; uint32_t v = reinterpret_cast<unsigned long>(pc); - const_cast<>() adds/removes const and volatile qualifiers
const int i = 1; int* j = const_cast<int*>(&i); *j++; - static_cast<>() cast from one type to another. It keeps const and volatile qualifiers unchanged. This is a safer more explicit cast than the implicit cast and is checked at compile time.
char c; isalpha(static_cast<unsigned char>(c));
- catch, try, throw: void foo() { if(doit()) return; throw std::out_of_Range(“couldn’t doit”);} throws an exception method which returns void. If throw is called without an operand it re-throws the pending exception or calls terminate(). Try catches a throw to handle it if possible. try {foo();} catch(const std::exception e) { std::cerr << e.what() <<‘\n’; } catch(…) {std::abort();}
- class struct private protected public this: classes default their members to private, struct to public. Private is for itself and friends. A class implicitly passes this pointer to all its non-static methods. template<T> class c { public: c(){}; protected: virtual f(){}; private: static s} c<T>::s; Private inheritance means base class public/protected members become private in the derived class: class b{}; class d: private b {}. Protected members can be used by the class, derived class and friends, using this pointer. Protected inheritance means base class public members are private for the derived class. Public members are accessible by all and represent the API of the class.
G++: struct attribute__((packed)) {…} S; forces contiguous alignment of fields, otherwise there’s no guarantee. Also classes with virtual functions will embed a vtable at the begining changing the layout. It matters when mapping a struct to an I/O buffer. - virtual: class c { virtual void foo() = 0; virtual void bar(){…};} declares foo, bar and c to be polymorphic. It means a derived class can replace the declaration of the function: class d : c {virtual void foo(){…}; virtual void bar(){c::bar();…}; }; d dd;. Declaring foo=0 means foo and c are also abstract i.e. c cannot be instantiated directly; A derived class like d must be created declaring its version of foo. d doesn’t need to re-declare its version of bar.
An unrelated use use of virtual is to share the inheritance of a class: class d: virtual c{}; class e: virtual c{}; class f: d,e {} - const object can’t be modified, can’t call non const member functions. const int* p points to a const value. int* const p pointer is const but value can be changed.
- continue do/for/while
- delete, delete[], new, new[] allocate and free memory and calls object constructors int*p = new(123) int; printf(“%d\n”, *p); delete p; int*a= new int[10]; delete[] a;
- double, float, long: floating point number sizeof(long double) >= sizeof(double) >= sizeof(long)
- if else if (a!=b) {…} else {…}
- enum enum e {a,b,c} does not support enum e : int { a=4,b,c}
- explicit disallows type conversions calling a constructor. class c{explicit c(int x){}}; c v(1); /*ok*/ c w=v; /*fails*/;c x; x = static_cast(c)(2); /*ok*/
- export declare an external template to compile separately from class using it export template<class T> class U { U(T x);}; export template<class T> U::U(T x) {…}
- extern declares a class as defined/compiled in another file. Can specify the language linkage extern “C” int random(); extern const double pi;
- for loop for{int i=0; i < max; ++i) {…}
- friend lets a class give a function, class or template access to its private members. class F {..} class C { friend class F; }
- goto jump to a label in the function while(1) { if (event) goto exit;} exit: clean();. It is customary to only do goto to the end of the function, just to cleanup loose ends.
- inline hint to tell compiler to expand the function where it was called. inline function must be defined in the same source file as it is used, before it is used. It can be defined in multiple files as long as it is the same definition (think: hpp header). inline plus(int a, int b){…} Note that large applications may have patching issues if an inline function is changed, and if all object files using it are not patched at the same time.
Note that inlined class methods should be fully written in the hpp header.
Note that in gcc the pragma attribute((always_inline)) is a stronger inline hint. - int, signed, unsigned, short, long declares and integer sizeof(long) >= sizeof(int) >= sizeof(short) >= sizeof(signed char) == 1. unsigned long should be the same size as a pointer.
Note: it is better to use stdint.h integers like uint8_t or uint32_t. - mutable allows a const class to contain a data member that can be modified
- namespace, using allows to use or set a name space. Classes define a name space that starts with the class name.
namespace n { const it x = 123; } namespace nn = n;
using namespace n; if (nn:x == x){…}; - register hint the compiler the variable will be used frequently and should be in a register.
- return returns from function, possibly a value.
void f1() { return;} int f2(){return 0;} - std::size_t sizeof() returns the memory size needed to store the object or type, including padding, computed at compile time.
int t[32]; std::size_t tSize = sizeof(t); - static In a class a static method doesn’t have a this pointer, and cannot access other non-static elements, static methods and variables are created once, and all objects of the class use the same one. It also can be referenced directly if it’s public.
class C{public: static int cnt; static void foo(){…}};
C::foo(); C::count = 0; - static creates at most once the object as a global object, but with internal instead of external linkage (not seen by the other elements that did not create it). Think of it as a locally visible object. A static object in a function is allocated and maintains its state across calls.
void f() { static int callCount=0; callCount++;} - switch case break default implements case statements.
switch(i) { case 1:…break; case 10:…break; default:….} - template declares a template, a template specialization or an instance of the template.
template<typename T> T foo() {…};
template<> bool foo<T>(T a){…};
template int foo<T>(T, T); - typedef create a shorthand synonym for an existing type.
typedef complexType<int,int> shortType; - typeid returns a reference to type_info describing the type via size_t hash_code() and const char *name(). Note typedef doesn’t create a new type_info. You can compare type_info typeid(x) == typeid(y).
#include <typeinfo>
template<class T> void debug(const &T t>{stc::clog<<typeid(obj).name() << ‘\n’;} - typename class tells the compiler the specifier is a type specifier. See templates. synonym to class here (try to use class only for classes though): template<typename C, class K> typename C::value_type foo(const C&c, const K&k) { return c+k;}
- union aggregate struct but can only store one of the fields at a time as they are stored in the same memory location. Cannot have virtual methods, nor inherit a base class. Its members cannot be static or a reference, nor have constructors/destructors/copy-assignment operators, virtual functions or virtual base classes. A union can only initialize its first member. typedef union {int i; char c;} u; u foo[2] = {1,2}; foo.c; Unions are often used to extract bytes or bits from larger types, for example converting endianess. It works in practice, however the standard doesn’t specify how each field overlaps or aligns, so you may want to avoid that or use compiler specific pragmas: union{ uint32_t i, uint8_t __attribute__ ((packed)) c[4]}.
- void abscence of a type, aka no parameter or return value, or a generic pointer. void foo(void) {…}; void*p =0;
- volatile tell compiler to not do read or write optimizations. Every reference to the object will correspond to actual memory I/O. Used for objects that are modified externally such as an I/O port or a shared variable between threads. volatile sig_Atomic_t isDone = false; volatile int*port; int v; while ((v=*port) == 0){} process(v); When an element is modified by an external event you must also take into account the atomicity of your read or write operation. I/O to bytes are usually natively atomic on all platforms, but it may not be the case for int or long. See architecture specific atomic assembly instructions, locks, etc.
- while, do performs a loop with a condition check at the begining or the end of the loop while(isTrue()){…} do{…}while(isTrue());
Preprocessor macros
Preprocessor macros are computed and resolved independently before compiling the C++ source code. This means the C++ code can use the result of the macros but not the reverse. Also macros can call each other but macro operators may not operate on macros (for example stringifying results of macros etc.).
- # null directive, aka comment # comment
- # stringify operator #define tostring(x) ‘[‘ #x “]:” int val = 3; std::cout << tostring(val) << val <<‘\n’;
- ## concat operator #define concat(x,y) x ## y std::cout << concat(val, 3) <<‘\n’;
- #define #undef declares a macro that can be used in the code, either as an unspecified macro that now exists, a macro variable or function. #define ok or #define one 1 or #define add(a,b) (a)+(b). A macro can also be undefined. Note: it is good practice to put parenthesis around macro parameters, as their input is not processed in the macro, but the macro is just substituted in place where it’s called and operator precedence might cause grief: #define inc(a) a++. inc(a+1) tries to compile a+1++ . It is common to protect header files to prevent them from being included multiple times with a define: foo.hpp: #ifndef FOO_HPP #define FOO_HPP …. #endif
- defined() returns 1 if the macro name is defined. You cannot give it a macro and expect it to be expanded. For example this won’t check if foo3 is defined: #define foocat(x) foo ## x #define foo3 #if defined(foocat(3))
- #if #elif #else #endif defines code regions that conditionally compile if the conditions are true. if and elif evaluate constant expressions #if defined(x86) … #elif defined(arm) … #else #error unsupported CPU #endif. In if/elif parameters: integers are cast to long or unsigned long; assignements are ineffective, unresolved identifiers are replaced by 0, which breaks casting, and use of sizeof, new, typeid, etc.
- #error makes the preprocessor print an error message and abort compilation
- #ifdef #ifdef x is shorthand for #if defined(x)
- #ifndef shorthand for #if !defined(x)
- #include insert a file in the code at this point. Used to include header files that declare types and functions. #include <foo.hpp> finds the header to insert from known compiler paths. #include “bar.hpp” inserts the file from the path specified (from one of the known roots).
- #line overrides the source line number tracked by the compiler in __LINE__. Don’t use unless you create autogenerated code. You can force a line number or number and file name #line 123 or #line 123 autogen.bash
- #pragma token give compiler directives. The syntax and semantics depend on the compiler.
Predefined macros in the C++ 98 standard:
- __cplusplus = 199711L for C++98
- __STDC__ == 1 for C compilers
- __DATE__reported as const char *
- __TIME__reported as const char *
- __FILE__ reported as const char *
- __LINE__ reported as a number
Templates
templates implement “parametric polymorphism”, and applies to function and class declarations and definitions.
A template has arguments that can be types, constant expressions or template references. Instantiating a template creates a specialization of a class or function for the template parameters.
template X<int i, typename T> T foo(T v) { return v+i};
std::cout << foo<3,short>(5) << ‘\n’; // prints 8.
You can also create a specialization of the template
template X<int i, char> T foo(T v) { return v[0]=i+’0′};
std::cout << foo<3,char>(“abc”) << ‘\n’; // prints “3bc”.
default arguments are OK, so is using a template parameter after it’s been defined
template<typename T, typename A = std::allocator<T>>
class C {void foo() const; }
template<typename T, typename A>
void C<T,A>::foo() {…};
Note you cannot pass a string literal to a template, you must name it first as such:
const char str[] = “msg”;
T<str> foo;
Templated template parameters are allowed if it references types from the template containing it.
template<template<class T, class A> class C, class T, class A>
void foo(C<T,A> c, T& i){..}
Function templates define a pattern for a function, and the function can be overloaded, even with non-template definitions, creating different implementations:
int min(int a, int b) {…};
template <typename T> T min(T a, T b) {..};
template <typename T, typename U> T min(T a, U b) {..};
int i = min(2,3);
short s = min<short>(2,3);
long l = min<long,short>(2,3);
STD library
Type support libraries
Concept library: C++20, ignore.
size_t, ptrdiff_t distance between 2 ptrs, NULL, size_t offsetof(struct, field) position of field. C++11: nullptr_t, max_align_t, byte enum
type_traits: C++11, Allows compile-time validation using properties of types. Useful with template arguments. if (std::is_same<int32_t, int>) {…}
limits: provides size limits std::numeric_limits<float>::max(), or std::numeric_limits<int>::digits10(). For float rounding issues see tinyness_before, epsilon, etc.
typeinfo: for runtime validation with type_info, type properties, comparisons, modifications and operations on type traits
Allocation
memory: underlying allocator for new, smart pointers like auto_ptr, atomic, align, garbage collection
new: new_handler, new, delete…
scoped_allocator: C++11.
memory_ressource: C++17.