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: