- Prefer using
{}
when initializing variables wherever possible, since it saves you from bugs due tonarrowing conversions
.// compilers may report warning for this, but it will succeed anyway unsigned int a = -5; // this will definitely result in compilation error unsigned int b {-5}; // redundant unsigned int c = {-5}; // not sure what's the use case for this one unsigned int d(-5);
- It's advised to not use
auto
as function return type, since they do not describe an obvious interface. - Prefer
enum class
overenum
since former is more strongly typed. - It's ok for
a
andb
to be non-constant arguments inconstexpr func(int a, int b)
. const
variables can be initialized at run time, which is what differentiates it fromconstexpr
variables.- Prefer
std::variant
over rawunions
for tagging type of currently held value.#include <variant> std::variant<int, double, std::string> v; v = "hello"; std::cout << "string ? " << std::holds_alternative<std::string>(v) << std::endl;
- Adding
noexcept
at the end of function declration results in callingstd::terminate()
whenever an exception is thrown from within that function. - Use
static_assert
for compile time assertions. - Use structured binding to
pack
orun-pack
only public members.struct point { int a; int b; } point func() { return {5, 6}; } auto [x, y] = func(); std::cout << "Pass ? " << (x == 5 && y == 6);
- Use
std::initializer_list
for instantiations (of custom classes) that require following semantics:std::vector v {1, 2, 3, 4}
- Methods defined inside class are inlined by default.
- Methods suffixed with
const
indicate that it doesn't modifythis
. Not doing this may result in weird errors when passing object as const reference. Typicallyerror: passing 'const xxx' as 'this' argument discards qualifiers [-fpermissive]
. - Abstract class should either provide a definition for virtual function or declare them pure.
class container { public: virtual double& operator[](unsigned long) = 0; virtual unsigned long size() = 0; // if braces are not provided, meaning it were a declration, it would've been an error! virtual ~container() {}; };
vtbl
or virtual function table, maintained by each class implementing virtual functions, contains indexed function pointers to all overridden virtual functions. The pointer tovtbl
itself is stored in the very beginning of the object.- Add explicit
override
suffix to method declarations to catch any mistakes in function declaration (e.g. spelling mistake), even though its use is purely optional. static_cast
(casting singed to unsigned, etc) vsdynamic_cast
(returns nullptr if cast is not valid, frequently used to recover type information. i.e. when determining wich derived class object does a base class pointer point to).- Compiler implicitly generates following for classes:
class container { public: container(); // default constructor container(const container&); // copy constructor container(container&&); // move constructor container& operator=(const container&); // copy assignment container& operator=(container&&); // move assignment ~container(); // destructor };
- Suffix constructor with
= default
or= delete
to be explicit about whether or not compiler should provide default implementations of various kinds of constructors. - Prefix constructor with
explicit
if it accepts only one argument to prohibit constructs like following:MyClass c = 5
. Abstract classes
should almost alwaysdelete
copy constructor and assignment.&&
is an rvalue reference, usually returned bystd::move
or return value of a function.- Compilers are designed to do
copy elision
upon initialization, hence move constructors may not be invoked as often. - A move constructor should leave the object to be
moved from
in a state where it can be destroyed. - Any new type should ideally define following conventional operations:
- Comparisons:
==
,!=
,<
,<=
,>
, and>=
- Container operations:
size()
,begin()
,end()
- Input and output operations:
>>
and<<
- User defined literals
swap()
- Hash functions:
hash<>
- Comparisons:
- Template type deduction can sometimes cause unexpected results. e.g.
std::vector<std::string> v1 {"Hello", "There"}; // will initialize with std::strings std::vector v1 {"Hello", "There"}; // will initialize with const char*
- There are cases where type cannot be deducted, use
deduction guide
in such cases:template <typename T> class container { // type deduction not needed for this constructor container(T); // type deduction needed for this since construtor arguments are not necessarily same as T template <typename U> container(U, U); }; // deduction guide template <typename U> container(U start, U end) -> container<int>;
- A function template can be a member function, but not a
virtual
member, since the compileer would not know all instantiations of such a template in a program to generate avtbl
. - Example of function object (aka
functor
),lambda expressions
and how they are used aspolicy objects
.// functor template<typename T> class less_than { const T val; public: less_than(const T& v): val{v} {} // overload call operator bool operator()(const T& x) const { return x < val; } }; // a function that expects functor as a predicate, i.e. a policy object template<typename Sequence, typename Callable> int count(const Sequence& s, const Callable& c) { int i = 0; for (auto& x: s) { if (c(x)) ++i; } return i; } int x = 3; std::cout << "Values less than " << x << ": " << count(std::vector {1, 2, 3, 4}, less_than {3}) << std::endl; // the lambda expression below will generate function object exactly like the one mentioned above std::cout << "Values less than " << x << ": " << count(std::vector {1, 2, 3, 4}, [&](int a) { return a < x; }) << std::endl;
- Use
auto
as type for arguments to writinggeneric lambda expressions
, but its support for function arguments was only added inc++20
. std::string
is an alias forstd::basic_string<char>
.std::string_view
is a read-only view of sequence of characters, designated by a pair of char* and length. i.e.std::string_view sv = {&"hello"[0], 3};