Since arrays are blocks of static memory their size must be determined at compile time. Therefore the number of elements specified in the declaration must be a constant expression.
Initializing an array with no values will initialize each element to
zero (e.g. int foo[5] = {};
). The compiler can determine
the number of elements from the initialization
(e.g. int foo[] = {1, 2, 3, 4, 5};
).
Arrays are passed by address even when not done explicitly. For
example, calling void foo(int Bar[]);
with
foo(array);
would not copy the entire array in memory.
However the declaration of functions with a multi-dimensional array as a
parameter must specify the size of all but the first dimension
(e.g. void foo(int Bar[][3][4]);
).
A vector represents a dynamically sized array that stores elements contiguously so that elements can be accessed using offsets.
In addition to C typdef
aliasing C++ provides the
using
keyword, which is more generic and has certain
benefits when using templates.
typedef uint32_t FOO;
using FOO = uint32_t;
In addition to the enumerated type inherited from C, C++ provides the
enum class
that provide type safety.
enum class Weekdays {
,
monday,
tuesday,
wednesday,
thursday
friday};
;
Weekdays work= Weekdays::monday; work
It is also possible to specify the underlying type:
enum class Weekend : char {saturday, sunday};
Although casting can be perfectly safe, like integer promotion or up/downcasting, there are also pitfalls. Those include casting the following:
uint64_t
to
uint8_t
)Explicit type casting, including c-like
(e.g. Foo foo = (Bar) bar
) or functional
(e.g. Foo foo = int (bar)
), is dangerous. C++ provides four
casting operators:
dynamic_cast
takes a pointer or reference. It allows
upcasts, but prevents incomplete downcasts. It returns a
nullptr
on failure unless it is used to convert a reference
type, in which case it throws a bad_cast
exception.static_cast
provides less overhead than a
dynamic_cast
by not making the same type-safety checksreinterpret_cast
is more liberal since it only checks
that the size of the target type is large enough to contain the
pointerconst_cast
manipulates the constness of the object
pointed to by the pointerNote that static_cast
and reinterpret_cast
only take pointers.
typeid
returns a type_info
that implements
the ==
and !=
operators. It also provides a
name()
member that returns a null-terminated character
sequence.
*foo = new Bar;
Foo typeid(foo).name; // Foo *
typeid(*foo).name; // Bar
Namespaces provide a more limited scope for variables, functions and
compound types that would normally be globally scoped. They can be
accessed from outside a namespace using the ::
scope
operator (e.g. namspaceName::x
).
The using namespace <name>
declaration allows
unqualified access to elements of a library.
In addition to classic C initialization
(e.g. int x = 0;
), C++ introduces constructor
initialization, which encloses the initial value between parentheses
(e.g. int x (0);
). Additionally, C++ 11 introduced uniform
initialization using curly braces (e.g. int x {0};
).
Specifying auto
instead of a type in a declaration tells
the compiler to figure out the type of the variable. Similarly, the
decltype
specifier will use the type of a declared variable
to declare a new variable. Note that using decltype
may
reduce the readability of the code.
Variables, functions and compound types may be globally or locally scoped. The later are declared inside code blocks.
Static storage like global or namespaced variables are initialized to zeroes when not explicitly initialized, unlike local variables that have an undetermined value when not explicitly initialized.
In addition to fundamental types, C++ provides numerous compound types like strings.
Arrays of characters can be declared as string literals using double quotes that are interpreted as literal constants. A null terminator is added to the end of the string.
Null-terminated character sequences can be transformed into strings
implicitly, while strings can be transformed using c_str()
or data
members of string
.
C++ provides an abstraction of input and output devices called
streams. The standard library provides several stream objects:
cin
, cout
, cerr
,
clog
. The simplest way to write to and read from a stream
is to use the insertion operator (i.e. <<
) and
extraction operator (i.e. >>
). However, it is
preferable to use getline(cin, x)
to get input from a
console.
A string can be converted to an integer using the
stringstream
in the <sstream>
standard
header:
("1024");
string x int y;
(x) >> y; stringstream
Any range-based structure, which consists of a sequence of elements and implements the being and end functions may be used with a range-based for loop.
{"Hello world!"};
string example for (auto c : example) {
std::cout << c;
}
std::cout << '\n';
To guarantee that parameters to a function that are passed by reference will not be modified, qualify the parameters as constant:
(const string& first, const string& second)
string concatenate{
return first + second;
}
Adding the inline
specifier to a function declaration
informs the compiler to insert the code within the function body into
each place where the function is called. The compiler is likely already
performing this optimization when necessary and is free to ignore the
specifier.
Assigning a value to a parameter in a function declaration
(e.g. int val=32
) indicates that the parameter is optional
and sets that value as a default. For example, a function declared with
two parameters, one of which has a default value, can be called with
either one or two arguments.
Function templates allow the definition of functions with generic
types. The function template needs to be prefixed with the
template
keyword followed by template parameters that
include class
or typename
that is in turn
followed by an identifier, which can be used as a type within the
function. When calling the function the user needs to specify the
template arguments as well as the function arguments.
It is important to note that no code is generated from a template definition in a source file. In order for the compiler to generate an actual function the template arguments must be determined. When the compiler finds the first call for a given type it instantiates the function template and then calls that instantiation every time the function is used for the type. Function templates can be explicitly instantiated by declaring the template with a particular type as an argument.
#include <iostream>
using namespace std;
template <class Foo, class Bar>
(Foo first, Bar second)
Foo sum{
;
Foo result= first + second;
result return result;
}
int main() {
int i = 5;
double j = 6, k;
= sum<int, double>(i, j);
k << "Explicitly passing the types of the arguments: " << k << std::endl;
cout = sum(i, j);
k << "Allowing the compiler to deduce the types: " << k << std::endl;
cout return 0;
}
The void
pointer points to a value without defining a
type and therefore cannot be dereferenced.
To point a pointer to nowhere without pointing to an invalid address,
which would cause undefined behavior, assign nullptr
to it
(e.g. int *p = nullptr;
).
Raw pointers don’t indicate length, ownership, destruction, or
whether it is dangling (i.e. it points to memory that no longer holds
the original value). There are four types of smart pointers in C++11:
std::auto_ptr
, std::unique_ptr
,
std::shared_ptr
, and std_weak_ptr
.
auto_ptr
is deprecated in favor of unique_ptr
s
and should only be used to compile code with C++98 compilers.
Unique pointers models unique ownership, meaning that there is and will always be only one pointer pointing to an object. This is enforced by making them non-copyable. It is better to construct a smart pointer and assign an object to it in one statement to prevent the object from being assigned to multiple pointers.
To dynamically allocate memory C++ provides the new
and
delete
operators. Unlike statically allocated memory
(e.g. a regular array that must be a constant expression) the size can
be determined at runtime.
Dynamic allocation is not guaranteed to be fulfilled by the system.
It can be checked by catching a bad_alloc
exception.
Alternatively, the return value can be compared to a
nullptr
when the declaration specifies a
nothrow
object. Catching exceptions is preferred for
efficiency.
int *foo;
= new int [5];
foo = new (nothrow) int [5]; // nullptr is returned instead of throwing `bad_alloc`
foo delete[5] foo;
However, new
and delete
should be used
sparingly in favor of following RAII by using smart pointers.
class Foo
{
int bar;
public:
void set_bar(int);
int increment_bar(int);
} foo; // Object of type Foo
// Use the scope operator to define a member function outside of the class
void Foo::set_bar(int x)
{
= x;
bar }
Class members that are either data or functions may have access
specifiers, which includes the keywords private
,
public
, or protected
. Members default to
private access, unless declared after another specifier.
private
: Only accessible from within the classprotected
: Accessible from within the class and
inherited classespublic
: Accessible from wherever the object is
visibleDefining a member function in a class definition causes it to be considered an inline function by the compiler, while defining it outside causes it to be considered a normal non-inline member function. The behavior is the same, but it may impact compiler optimizations.
Accessing members before they have been initialized returns an undefined value. Object initialization and storage allocation occurs within a constructor that has no return values.
class Foo
{
int bar;
(int);
Foo};
::Foo()
Foo{
= 32;
foo }
::Foo(int x)
Foo{
= x;
foo }
int main()
{
(15); // Functional form
Foo one= 20; // Assignment form
Foo two ; // Default constructor
Foo three(); // WRONG: a function declaration
Foo four{}; // Uniform initialization
Foo five}
Note that uniform initialization has a preference of
initializer_list
as a type.
Members of a class can be initialized before the body of a constructor using a semicolon.
class Foo
{
int bar, baz;
public
(int r, int z) : bar{r}, baz{z} {}
Foo};
int main()
{
*foos = new Foo[2] {{1, 2}, {3, 4}};
Foo }
Initializing a object without new
allocates it within
local scope and typically on the stack, which gets destroyed
automatically when the local scope ends. In contrast, using
new
allocates a block of memory and returns an address that
is stored in a pointer. The object is normally stored on the heap and
requires a call to delete
to be destroyed.
Note that struct
acts the same as class
,
except that members of a struct
default to public, while
members of a class
default to private. Similarly, a
union
is a class with members that default to public, but
of course it can only store one data member at a time.
The keyword this
is used within a class’s member
functions to refer to the object itself.
A static
member of a class is shared between all
instances of that class. Since they have the same properties as
non-member variables, but are within the scope of the class they must be
initialized outside the class (e.g. int Foo::bar = 0;
).
Similarly, static member functions can not access non-static members of
the class or this
.
When an object of a class is declared as const
its
members are restricted to be read-only outside the object, but they
still may be modified in the object and initialized by calling a
constructor. Member functions in this case may only be called if they
are specified as const
(e.g. int foo() const {}
). As a rule of thumb,
const
members shall not modify the state of an object. Two
member functions with identical signatures may be declared if only one
is specified as const
.
It is possible to create class templates in much the same way as one can create function templates.
template <class T>
class Foo
{
, baz;
T barpublic:
(T r, T z) : bar{r}, baz{z} {}
Foo};
Class templates may be overloaded depending on the type of template arguments it is passed. A template specialization is written in addition to the original class definition. For example, given the above implementation one could add the following:
template <>
class Foo <char>
{
char bar, baz;
public:
(char r, char z) : bar{r}, baz{z} {}
Foo};
When a object of a class without a constructor is declared, but not
initialized with any arguments (e.g. Foo foo;
) then it’s
implicit default constructor is called. It must be called explicitly
(i.e. you need to write a constructor without arguments) when a
constructor exists with one or more arguments.
Conversely, a destructor is called when an object is cleaned up (e.g. the object loses scope).
Copy constructors take a reference to the class itself
(e.g. Foo::Foo(const Foo&);
). The default copy
constructor does a shallow copy, which involves just copying the class
members. A deep copy, which may involve handling pointers so that both
objects don’t hold a pointer to the same object, requires writing a
custom copy constructor. When writing a custom copy constructor it is
probably also worth overloading the copy assignment operator
(e.g. Foo& operator= (const Foo&);
).
Move constructors are called on unnamed objects that are typically
return values or type-casts. Note that compilers use Return Value
Optimization, which may mean the move constructor doesn’t get called
(e.g. auto bar = foo();
). Move constructors and assignments
take a rvalue reference to the class. For example,
(Foo&&); // Move constructor
Foo operator= (Foo&&); // Move assignment Foo
Moving often means simply copying the pointer to an object that has been dynamically allocated to another object of the same type. For example,
(Foo&& f) : bar(f.bar) {f.bar=nullptr;} Foo
A function that is declared in a class with the friend
keyword may access the private and protected members of that class, but
is not considered a member function.
A class that is included in the definition of another class with the
friend
keyword (e.g. friend class Foo
) may
similarly access the private and protected members of the other
class.
For example, the derived class, Bar
, inherits from the
base class, Foo
:
class Bar: public Foo {};
In this case public
allows Bar to inherit all the
members with the same levels they had in Foo. Alternatively, replacing
it with protected
means that all public
members of Foo will be inherited as protected
. Finally,
private
follows as one would expect. If a access level is
not specified then classes default to private
, while
structs default to public
.
The base class default constructor will be called unless otherwise specified.
class Bar : public Foo {
public:
(int a): Foo(a) {}
Bar}
This example will call the corresponding constructor of the base
class, Foo
.
Multiple inheritance is also supported as follows:
class Bar : public Foo, public Baz;
A pointer to a derived class is type-compatible with a pointer to its base class, but only members inherited from the base class can be accessed.
If the declaration of a member function in a base class is preceded
with virtual
then it can be redefined in a derived class.
Both of the base class and derived class are referred to as polymorphic
classes.
A base class which contains virtual functions without definitions
(i.e. pure virtual functions) are referred to as abstract. It may only
be used as a base class and therefore can not be used to instantiate an
object. Note that the definition of the function is replaced by
=0
(e.g. virtual int foo() =0;
).
Implicit conversions can be supported using three types of member functions:
class Foo {};
class Bar {
public:
(const Foo& foo) {} // Constructor (e.g. Bar bar = foo)
Bar operator= (const Foo& foo) {} // Assignment (e.g. bar = foo)
Bar operator Foo() {} // Conversion (e.g. foo = bar)
}
Adding explicit
to the beginning of a constructor
declaration that performs a conversion prevents implicit conversions for
arguments of any function that takes an argument of that type.
Custom exceptions should inherit from <stdexcept>
or when necessary std::exception
.
#include <exception>
#include <stdexcept>
#include <string>
class CustomError : public std::exception {
public:
(std::string msg) : msg_(msg) {}
CustomError~CustomError() noexcept {}
virtual const char* what() const noexcept {
return msg_.c_str();
}
private:
std::string msg_;
};
class CustomRuntimeError : public std::runtime_error {
public:
(std::string const &msg) : std::runtime_error(msg) {}
CustomRuntimeError};
Other topics of note: