C++ 11 - Overview PDF
C++ 11 - Overview PDF
Frank D. Luna
November 30, 2013
C++ was officially standardized in 1998 (C++98). Recently, the C++ language standard was
updated in 2011 (C++11) to include new language features and extensions to the standard
library. This article is not meant to be a comprehensive explanation of all C++11 features.
Instead, it explains the new parts of C++11 that will be helpful in your day-to-day programming.
Please see the “References and Further Reading” section at the end of this paper for some
resources that contain a more encyclopedic overview of the C++11. Note that not all compilers
have added support for the C++11 features. We use Visual Studio 2013 (VS13) for the examples
in this paper, which supports the majority of C++11 features.
CONTENTS
1 nullptr .........................................................................................................................................................................3
6 Auto ............................................................................................................................................................................6
8 std::initializer_list .....................................................................................................................................................11
1
13 override and final....................................................................................................................................................18
16 using........................................................................................................................................................................26
28 std::bind ..................................................................................................................................................................55
2
1 NULLPTR
Besides being more readable, it also solves an ambiguity problem that arises if a function has
two overloads where one overload takes an integer and the other overload takes a pointer. In
this situation, passing a null pointer using the literal 0 results in the integer overload being
invoked instead of the pointer overload. On the other hand, using nullptr calls the expected
pointer overload. The following program demonstrates:
#include <iostream>
#include <string>
void MyFunc(int x)
{
std::cout << "int overload" << std::endl;
}
void MyFunc(int* p)
{
std::cout << "pointer overload" << std::endl;
}
#define NULL 0
int main()
{
MyFunc(NULL); // Calls int overload
MyFunc(nullptr); // Calls int* overload
}
int overload
pointer overload
Press any key to continue . . .
2 LONG LONG
A new intrinsic integer variable type long long is specified to be at least a 64-bit integer.
3
#include <iostream>
#include <string>
int main()
{
unsigned long long bigInt = 0xffffffffffffffff;
sizeof(bigInt) = 8
bigInt = 18446744073709551615
Press any key to continue . . .
std::vector<std::vector<int>> table;
The workaround was to simply include a space between the ending “>>” symbol:
// C++98 Workaround
std::vector<std::vector<int> > table;
The reason for this is that C++98 always interprets the “>>” in this situation as the right shift
operator. C++11 fixes this oddity so that the “>>” in this situation can be interpreted as you
would expect it to (just closing the template arguments).
4 STATIC ASSERT
The static_assert keyword is like a compile time version of the assert function. The first
parameter is a bool expression that can be evaluated at compile time, and the second
parameter is an error message the compiler will output if the bool expression is false. It is
mostly useful with templates when you need to validate certain properties about the template
arguments. For example, your template code may expect the argument type to be default
constructible; you can validate this property at compile time using a combination of
static_assert and type traits (#include <type_traits>):
#include <iostream>
#include <type_traits> // for is_default_constructible
template<typename T>
class C
{
4
public:
static_assert(std::is_default_constructible<T>::value,
"T must be default constructible.");
};
class A
{
public:
A(int x) : data(x) { }
int data;
};
int main()
{
C<int> c1; // ok, int is default constructible
C<A> c2; // error, A has no default constructor
}
This gives the compiler error: “error C2338: T must be default constructible.” This is because A
does not have a default constructor.
The following contrived example has a template function that expects the argument to
be a pointer.
#include <iostream>
#include <type_traits> // for is_pointer
template<typename T>
void MyFunc(T ptr)
{
static_assert(std::is_pointer<T>::value, "Function expects pointer type.");
int main()
{
int v = 5;
int* p = &v;
This gives the compiler error: “error C2338: Function expects pointer type.” This is because v is
not a pointer type.
Consider the following code that counts how many times a value occurs in an array and vector:
#include <iostream>
#include <vector>
5
#include <algorithm>
int main()
{
const int ArraySize = 10;
int arr[ArraySize] = { 4, 3, 3, 4, 1, 4, 6, 3, 2, 3 };
std::vector<int> v(&arr[0], &arr[ArraySize]);
std::cout << "2 is counted " << cnt1 << " times." << std::endl;
std::cout << "3 is counted " << cnt2 << " times." << std::endl;
}
2 is counted 1 times.
3 is counted 4 times.
Press any key to continue . . .
The issue is that the array version has a much different syntax than the vector version. C++11
introduces non-member begin and end functions for a uniform syntax. The standard library
provides non-member begin and end function overloads for all the standard containers, as well
as arrays:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
const int ArraySize = 10;
int arr[ArraySize] = { 4, 3, 3, 4, 1, 4, 6, 3, 2, 3 };
std::vector<int> v(std::begin(arr), std::end(arr));
std::cout << "2 is counted " << cnt1 << " times." << std::endl;
std::cout << "3 is counted " << cnt2 << " times." << std::endl;
}
If you create new containers or if you have 3rd party library containers, then you can define
non-member begin and end function overloads so that they also can have the same uniform
syntax.
6 AUTO
6
Consider the following code:
#include <iostream>
#include <string>
#include <map>
int main()
{
std::map<std::string, int> map;
std::map<std::string, int>::iterator
is verbose and repetitive because we already know what the type of it must be—it must
match the return type of std::begin overloaded for std::map; otherwise, we would get a
compiler error. The auto keyword allows the compiler to infer the type automatically. That is,
the following is equivalent to the above code:
#include <iostream>
#include <string>
#include <map>
int main()
{
std::map<std::string, int> map;
Using auto for things like iterators saves a fair amount of typing, especially when templates are
involved or function pointers, but it can also be used in simpler cases:
7
auto eng = new SoftwareEngineer();
It should be stressed that using auto is still static type-safe code; that is, the C++98 version and
C++11 version are completely identical. In fact, in Visual Studio, if you hover the mouse cursor
over the variable, the intellisense will show the inferred type.
Note: Using auto never does a type conversion. The type inferred by auto always matched the type of the
initialization expression.
It is easy to make a mistake with auto when dealing with functions that returns references.
Suppose you have a function that returns a reference and you use auto like so:
Then the variable x in this case will actually be of type Item. This is because it is perfectly legal
to copy the value stored in the reference into a new Item variable. To explicitly say you want a
reference to the returning object type, then you use the syntax auto&:
Here is another benefit of using auto that makes the code more maintainable. Suppose you
have a function that returns a std::string and you always capture the return value using
auto in all places where you call Person::GetName:
8
Now suppose that you want to upgrade your strings to use std::wstring (wide character
strings). Because you used auto, you just have to update the return type of
Person::GetName to return a std::wstring and all the code where you have
Then you would have to update all the places where this code exists and rename
std::string to std::wstring.
Remark: The auto keyword also comes into play in more advanced template scenarios where the actual return
type from a function might not be easily determined by a programmer, but the compiler has no problem figuring it
out as part of its code analysis. You must also use auto to declare and initialize a variable to a lambda expression
(whose type is only known by the compiler).
Similar to auto, the decltype keyword yields the “declared type” of an expression at compile
time. In contrast to auto, decltype does not require creating a variable and it follows
different rules of type deduction. The decltype keyword is mostly useful for authors of
template libraries. Therefore, we will not go into the details of decltype’s deduction process,
but just give two simple examples so that you can get a flavor of decltype.
Example 1
#include <iostream>
#include <complex>
int main()
{
std::complex<float> c{ 1.0f, 2.0f };
float r = 5.0f;
std::cout << "var1 = (" << var1.real() << ", " << var1.imag() << ")" << std::endl;
std::cout << "var2 = " << var2 << std::endl;
}
9
var2 = 6
Press any key to continue . . .
Note that decltype() results in a type (determined at compile time). Therefore, it can be
used to specify a variable type.
Example 2
This next example will show decltype used in the so-called suffix return type syntax or
trailing return type syntax. In this return syntax, we specify auto in the place where we usually
put the return type. This tells the compiler that we will specify the actual return type later (as a
suffix). Then to specify the suffix return type, we use the syntax “-> Type” after the
parameter list. Using the suffix return type syntax allows us to use decltype to specify the
return type. This is useful in template programming when the return type can depend on more
than one template arguments.
#include <iostream>
#include <complex>
int main()
{
std::complex<float> c{ 1.0f, 2.0f };
float r = 5.0f;
// Gives Mul version: std::complex Mul(const std::complex& lhs, const float& rhs);
auto p2 = Mul(c, r); // p2 has type std::complex
// Gives Mul version: float Mul(const float& lhs, const float& rhs);
auto p3 = Mul(r, r); // p3 has type float
}
10
The reason is that lhs and rhs have not been declared yet so the compiler does not know
what lhs and rhs are. By moving the return type to the end after the parameter list, the
compiler knows about lhs and rhs and can evaluate decltype(lhs*rhs).
8 STD::INITIALIZER_LIST
The standard library introduces a new variable type std::initializer_list that can be
assigned a variable number of arguments of the same type within braces. The following code
gives a simple example.
#include <iostream>
int main()
{
std::initializer_list<char> charList = { 'a', 'b', 'c' };
std::cout << "charList size = " << charList.size() << std::endl;
for(auto it = std::begin(charList); it != std::end(charList); ++it)
std::cout << *it << " ";
std::cout << std::endl;
std::initializer_list<int> intList = { 1, 2, 3, 3, -1 };
std::cout << "intList size = " << intList.size() << std::endl;
for(auto it = std::begin(intList); it != std::end(intList); ++it)
std::cout << *it << " ";
std::cout << std::endl;
}
charList size = 3
abc
intList size = 5
1 2 3 3 -1
Press any key to continue . . .
The interface for std::initializer_list is quite simple. It supports begin and end
iterators, and a size member function.
The std::initializer_list type becomes particularly convenient for container like
classes. For instance, the STL containers have been updated in C++11 to have a constructor
that takes a std::initializer_list. This allows us to construct a std::vector as
follows:
If you define your own container class, you can provide a std::initializer_list
constructor so that your container class can be constructed via a std::initializer_list:
#include <iostream>
template<typename T>
11
class DynamicArray
{
public:
explicit DynamicArray(int count)
{
mCount = count;
mData = new T[mCount];
}
DynamicArray(std::initializer_list<T> l)
{
mCount = l.size();
mData = new T[mCount];
int i = 0;
for(auto it = std::begin(l); it != std::end(l); ++it)
mData[i++] = *it;
}
// Copy constructor.
DynamicArray(const DynamicArray& rhs)
{
mCount = rhs.mCount;
mData = new T[mCount];
for(int i = 0; i < mCount; ++i)
mData[i] = rhs.mData[i];
}
~DynamicArray()
{
delete[] mData;
mData = nullptr;
}
// Copy assignment.
DynamicArray& operator=(const DynamicArray& rhs)
{
if(&rhs == this)
return *this;
delete[] mData;
mCount = rhs.mCount;
mData = new T[mCount];
for(int i = 0; i < mCount; ++i)
mData[i] = rhs.mData[i];
return *this;
}
private:
int mCount;
T* mData;
12
};
int main()
{
DynamicArray<int> myArray({ 6, -1, 7, 8, 9, 22 });
for(int i = 0; i < myArray.Count(); ++i)
std::cout << myArray[i] << " ";
std::cout << std::endl;
6 -1 7 8 9 22
123
Press any key to continue . . .
You can even have functions that take a std::initializer_list, which allows for syntax
like so:
#include <iostream>
#include <string>
void PrintStringList(std::initializer_list<std::string> l)
{
for(auto it = l.begin(); it != l.end(); ++it)
std::cout << *it << " ";
std::cout << std::endl;
}
int main()
{
PrintStringList({ "initializer_list", "is", "pretty", "cool." });
}
Note: We pass a std::initializer_list by value instead of reference because they are handled specially in memory,
and so are cheap to copy.
9 UNIFORM INITIALIZATION
Recall that plain old data types can be initialized using the brace initialization list syntax:
#include <iostream>
#include <string>
13
struct Data
{
int x;
char y;
float z;
std::string s;
};
int main()
{
Data d = { 2, 'c', 3.14f, "Hello" };
std::cout << d.x << ", " << d.y << ", " << d.z << ", " << d.s << std::endl;
}
Also recall that arrays can be initialized with the same syntax. Furthermore, in the previous
section, we saw that we can create a std::initializer_list, which is useful when we
want to initialize a container. However, the syntax of constructing an object is different.
Suppose we had a Person class with a constructor that takes a name, age, and sex, then an
instance would be constructed as follows:
#include <iostream>
#include <string>
class Person
{
public:
Person(std::string name, int age, char sex) :
mName(name),
mAge(age),
mSex(sex)
{
std::cout << "Person constructor called: ";
std::cout << "(" << mName << ", " << mAge << ", " << mSex << ")" << std::endl;
}
private:
std::string mName;
int mAge;
char mSex;
};
int main()
{
Person p1( "Bilbo", 100, 'M' );
14
C++11 extends the brace initialization syntax to invoke constructors. The following code
produces the exact same output as the previous program but uses uniform initialization syntax
to invoke the constructors.
int main()
{
Person p1{ "Bilbo", 100, 'M' };
We now have a “uniform initialization” syntax. Observe now that an STL container can be
constructed using the following uniform initialization syntax. For example:
Note: std::initializer_list constructors take precedence over other constructors. For example, for
std::vector we have the following:
10 RANGE-BASED FOR-LOOPS
C++11 adds a simpler kind of for-loop called a range-based for-loop. The following code
illustrates several examples:
#include <iostream>
#include <string>
#include <vector>
#include <map>
int main()
{
float arr[] = { 1.1f, 1.2f, 1.3f, 1.4f };
std::vector<int> vec{ 1, 2, 3, 4, 5, 6, 7 };
15
// Capture by value for read-only.
for(float f : arr)
std::cout << f << " ";
std::cout << std::endl;
Observe that the use of auto is convenient in range-based for-loops, but not necessary.
Note: If you use auto instead of auto&, it is legal but then you are working with a local copy of each element in
the container, and not a reference to the actual element. Sometimes this is desired, but can lead to bugs if you are
trying to modify each element in the container, as you will only modify the copy.
for(auto& x : C) { ... }
16
for(auto it = std::begin(container); it != std::end(container); ++it)
{
// If you used auto instead of auto&, then this line
// would be auto x = *it to get a copy of *it.
auto& x = *it;
// do something with x.
}
In fact this is the case. For any container to support the range-based syntax, is must provide
begin and end iterators, and those iterators must support operator !=, operator ++, and the
dereferencing operator *. Thus, if you need to create your own custom container type, you can
implement this interface and your container will automatically work with range-based for-loop.
Note that arrays have std::begin and std::end overloads, and since arrays are
accessed via pointers, they support operators !=, ++, and *. Therefore, range-based for-loops
work with arrays as well.
1. They do not define a new named scope, so it is easy to get name collisions.
2. They are implicitly converted to an integral type.
3. The underlying integer type cannot be specified explicitly.
4. They cannot be forwarded-declared.
C++11 fixes these problems by introducing a strongly-typed enumerated type, which uses the
enum class syntax. The following example demonstrates:
#include <iostream>
int main()
17
{
// No name clash because enum class introduced a named scope, so
// DayType::Saturday and WeekendDayType::Saturday are different names.
DayType day = DayType::Saturday;
WeekendDayType weekendDay = WeekendDayType::Saturday;
If the underlying type of an enum class is not specified, then it defaults to int. The following
code shows forward-delarations:
To avoid the need for escape characters, C++11 introduces the ability to specify raw string
literals:
#include <iostream>
#include <string>
int main()
{
std::string s1 = R"("quoted string")";
std::string s2 = R"(C:\data\path\file.txt)";
"quoted string"
C:\data\path\file.txt
Press any key to continue . . .
Everything inside the parenthesis of the R"()" is part of the string literal (note the parenthesis
are needed).
We discuss two contextual keywords in this section called override and final. Contextual
keywords have special meanings only in certain contexts; otherwise they are treated as normal
identifiers (for example, variable names). The reason they could not be made keywords
18
outright is because of existing code prior to C++11 that may have used these names as
identifiers.
The override identifier allows you to specify that you are overriding a virtual function, thereby
making your intent clear to other programmers. Besides providing self-documenting code, this
can prevent a subtle error if you change the signature of a method in the base class but forget
to update the signature in derived classes. Consider the following program where a virtual
function is overridden without using the override keyword:
#include <iostream>
#include <string>
class Material
{
public:
virtual void Apply()const
{
std::cout << "Material::Apply()" << std::endl;
}
};
int main()
{
Material* d = new DefaultMaterial();
Material* m = new MetalMaterial();
d->Apply();
m->Apply();
delete d;
delete m;
}
DefaultMaterial::Apply()
MetalMaterial::Apply()
19
Press any key to continue . . .
This program works as expected. Now suppose that the program requirements change and
that MetalShader::Apply needs to change the internal object state. Apply can no longer
be a const method in this case. Suppose the programmer is under time pressure and stress,
and so to implement this change removes the const from MetalShader::Apply and
Material::Apply, but forgets to remove it from DefaultMaterial::Apply. Although
making such a mistake in this simple example seems unlikely, with a large code base where
many materials inheriting from Material and all the classes are spread out in separate .h/.cpp
files, making this kind of mistake becomes more probable. The program still compiles and runs,
but now has an error:
#include <iostream>
#include <string>
class Material
{
public:
virtual void Apply()
{
std::cout << "Material::Apply()" << std::endl;
}
};
int main()
{
Material* d = new DefaultMaterial();
Material* m = new MetalMaterial();
d->Apply();
m->Apply();
delete d;
delete m;
}
Material::Apply()
20
MetalMaterial::Apply()
Press any key to continue . . .
#include <iostream>
#include <string>
class Material
{
public:
virtual void Apply()
{
std::cout << "Material::Apply()" << std::endl;
}
};
int main()
{
Material* d = new DefaultMaterial();
Material* m = new MetalMaterial();
d->Apply();
m->Apply();
delete d;
delete m;
}
This results in the compilation error: “error C3668: 'DefaultMaterial::Apply' : method with
override specifier 'override' did not override any base class methods.”
21
Note: The override keyword comes last in the method declaration. Moreover, it only appears in the class
definition. If you are separating a member function declaration and implementation, you only specify the
override keyword in the class definition (.h file) not as part of the implementation (.cpp file).
The final contextual keyword can be used on a class to prevent it from being derived from.
The following code gives the error: “error C3246: 'DerivedFromFinal' : cannot inherit from
'FinalClass' as it has been declared as 'final'.”
#include <iostream>
#include <string>
int main()
{
}
It is good practice to mark classes as final when they are not designed for inheritance or you
do not want clients to be allowed to inherit from the class.
Similarly, you can use the final identifier on a method to prevent it from being
overridden. The following code gives the error: “error C3248: 'Base::Func': function declared as
'final' cannot be overridden by 'Derived::Func'.”
#include <iostream>
#include <string>
class Base
{
public:
virtual float Func(float x)final
{
std::cout << "Base::Func()" << std::endl;
}
};
22
};
int main()
{
}
You might use the final keyword on a method in an inheritance hierarchy when you have
reached a point that you do not want the method to be overridden by further subclasses.
Note: The final keyword comes last in the method declaration. Moreover, it only appears in the class definition.
If you are separating a member function declaration and implementation, you only specify the final keyword in
the class definition (.h file) not as part of the implementation (.cpp file).
Sometimes a class designer does not want objects of that class to be copyable. One example
where this is the case is when the objects are memory intensive and duplicates are never
needed. For example, if you have a game that has sound effects, you only need one copy of
each sound effect in memory. Multiple areas of code can reference the same sound effect, but
the sound effect data should not be duplicated. Thus you implement some sort of manager
class that controls creating new objects so that duplicates are not created and such that you
can have multiple references to the same object. In C++98 the common practice to disable
copying is to make the copy constructor and assignment operator private. Then if the client
tries to copy, they will get an error. The following example illustrates:
class Resource
{
public:
Resource(){}
private:
Resource(const Resource& rhs);
Resource& operator=(const Resource& rhs);
};
int main()
{
Resource r1;
Resource r2 = r1;
}
The above code results in the compile time error: “error C2248: 'Resource::Resource' : cannot
access private member declared in class 'Resource'.” In addition, observe that because we
declared the copy constructor, the compiler no longer gives us the default constructor
automatically, so we had to implement a default constructor explicitly.
C++11 simplifies the above with the default and delete keywords:
23
class Resource
{
public:
int main()
{
Resource r1;
Resource r2 = r1;
}
Now we get the error: “error C2280: 'Resource::Resource(const Resource &)' : attempting to
reference a deleted function.”
Note that you can use the =default on any member function the compiler can
generate for you to get the compiler generated version; then you do not need to implement it
yourself if the compiler generated version suffices.
15 MEMBER INITIALIZATION
C++11 allows you to initialize a member variable in the class definition; this is called in-class
initialization. This can save a considerable amount of redundancy in classes that have multiple
constructors. If a constructor does not initialize a member variable but there is an in-class
initialization, then the in-class initialization is used; on the other hand, if a constructor does
initializes a member variable and there is an in-class initializer for that variable, then only the
constructor initialization is invoked. The following example demonstrates in-class initialization.
#include <iostream>
#include <string>
#include <vector>
class Rectangle
{
public:
Rectangle() = default;
Rectangle(int x, int y);
Rectangle(int x, int y, int w, int h);
Rectangle(std::string name);
Rectangle(std::string name, int x, int y);
Rectangle(std::string name, int x, int y, int w, int h);
private:
std::string mName = "Default";
24
int mX = 0;
int mY = 0;
int mWidth = 1;
int mHeight = 1;
};
Rectangle::Rectangle(int x, int y) :
mX(x),
mY(y)
{
}
Rectangle::Rectangle(std::string name) :
mName(name)
{
}
int main()
{
Rectangle r1;
Rectangle r2{ 5, 2 };
Rectangle r3{ 0, 0, 4, 8 };
Rectangle r4{ "rect4" };
Rectangle r5{ "rect5", 2, -1 };
Rectangle r6{ "rect6", -3, 1, 3, 3 };
r1.Print(std::cout);
r2.Print(std::cout);
25
r3.Print(std::cout);
r4.Print(std::cout);
r5.Print(std::cout);
r6.Print(std::cout);
}
Observe that we only have to set the variables in the constructor initialization list that
correspond to the constructor parameters. The other variables get default values as specified
through the in-class initialization.
You can initialize more complex variables in-class using any initialization syntax. For
example, you can even initialize vectors. Note that due to a current bug in the initial release of
VS13 a work around syntax must be used:
class C
{
public:
// Does not currently compile in VS13 due to a bug
//std::vector<int> Data{ 1, 2, 3, 4 };
16 USING
#include <iostream>
#include <vector>
int main()
{
using veci = std::vector<int>;
veci v{ 1, 2, 3, 4 };
for(auto e : v)
std::cout << e << " ";
std::cout << std::endl;
}
26
Note that the type alias veci is visible in scope to the main function. Similarly, a type alias
defined in a class definition is visible in scope to that class.
17 DELEGATING CONSTRUCTORS
A class that provides many constructors often has to repeat some code in each constructor.
This can lead to errors and hard to maintain code. One way to avoid this repetition in C++98
would be to make a helper method that contained the common code. However, C++11 adds
delegating constructors which allows you to delegate work to another constructor. The
following example illustrates delegating constructors:
#include <iostream>
#include <string>
#include <algorithm> // for std::max
class Size
{
public:
Size() : Size(1, 1) { std::cout << "Size()" << std::endl; }
Size(int size) : Size(size, size) { std::cout << "Size(int size)" << std::endl; }
Size(int width, int height)
{
mWidth = std::max(width, 1);
mHeight = std::max(height, 1);
private:
int mWidth;
int mHeight;
};
int main()
{
Size s1;
Size s2(5);
Size s3(3, -2);
std::cout << "s1 = (" << s1.Width() << ", " << s1.Height() << ")" << std::endl;
std::cout << "s2 = (" << s2.Width() << ", " << s2.Height() << ")" << std::endl;
std::cout << "s3 = (" << s3.Width() << ", " << s3.Height() << ")" << std::endl;
}
27
Size(int width, int height)
s1 = (1, 1)
s2 = (5, 5)
s3 = (3, 1)
Press any key to continue . . .
Compare this code to how the Size class might be implemented in C++98:
class Size
{
public:
Size() :
mWidth(1),
mHeight(1)
{
std::cout << "Size()" << std::endl;
}
Size(int size)
{
mWidth = std::max(size, 1);
mHeight = mWidth;
private:
int mWidth;
int mHeight;
};
18 TYPE TRAITS
With template programming, we sometimes want to get information about a type. For
example, is the type polymorphic (has a virtual function), is the type an array, or is the type
derived from a particular base class. The compiler knows this information when it compiles the
code, so it seems like we should be able to get this information at compile time. The
28
<type_traits> header allows us to query information about a type at compile time. This is
often combined with static_assert to validate some assertions about the types we are
working with at compile time. We have already seen an example of type traits in the
static_assert example, so we will not elaborate further as this is a niche area mostly for
template library implementers. However, we encourage the reader to skim over some
reference material on the subject such as the webpage:
http://www.cplusplus.com/reference/type_traits/.
A constructor that takes one parameter is called a conversion constructor. For example,
suppose that you had a class A and a class B, and suppose that A has a constructor that takes a
single B parameter. This constructor is called a conversion constructor because it takes a B
parameter and constructs an instance of type A out of it, thus converting a B object into an A
object. The conversion constructor can be called implicitly. Having automatic conversions can
sometimes lead to subtle error or make the intent of the code less clear. Therefore,
programmers sometimes want to require any conversions to be made explicit so that the intent
of the programmer is clear and the chance of error is reduced. C++98 allows a constructor to
be prefixed with the explicit keyword, which means the conversion constructor must be
called explicitly or with an explicit cast. However, C++98 has no mechanism to prevent an
implicit conversion from a conversion operator. C++11 fixes this by adding explicit conversion
operators.
To illustrate explicit conversions, we will use a simplified Color class. One way a color
can be represented is by 4 floats in the range [0, 1] storing the red, green, blue, and alpha
components. Another common color representation is to use 4 bytes packed into a 32-bit
unsigned integer, where the top byte stores the alpha channel, the next byte stores the red
channel, the next byte stores the green channel, and the low byte stores the blue channel; in
other words, the 32-bits are divided up as follows: [32] aaaaaaaa rrrrrrrr gggggggg
bbbbbbbb [0]. In graphical programming, both formats are used, so it is convenient to be
able to convert between the two. However, if we allow implicit conversions, the following
syntax is legal:
int Square(int x) { return x*x; }
...
Color c1 = 5;
int s = Square(c1);
When glancing at this code, assigning a number to a Color like that may seem confusing. Was
it a careless mistake or intended? The ability to silently convert from a Color to an integer
means that a Color can be passed to functions that were designed for integers representing
numbers, not colors. Is this an error or does the programmer really want to square the integer
representation of the color? Using explicit conversions fixes these problems. If these were
mistakes, the compiler would now generate an error. If they were intended, the code makes
that intent clear by requiring an explicit cast in order to compile:
29
using uint = unsigned int;
...
Color c1 = (Color)5;
int s = Square((uint)c1); // Odd, but compiles and intent made clear
#include <iostream>
#include <string>
class Color
{
public:
Color() = default;
30
return color;
}
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 1.0f;
};
int main()
{
// Cannot convert implicitly if constructor marked explicit.
//Color c1 = 255; // error: cannot convert from 'int' to 'Color'
Color::Color(uint color)
Color::Color(uint color)
Color::operator uint()
Color::operator uint()
Press any key to continue . . .
20 RANDOM NUMBERS
C++11 introduces a brand new library for random number generation that is much better than
the C library function rand(); in fact, see [STL2013] for reasons why rand() is bad. The
library has classes suitable for high quality random number generation for general purpose
applications to specific scientific domains. For the scope of this paper, we will focus on the
classes that you would use for games and general purpose applications. See a C++ reference
(such as http://www.cplusplus.com/reference/random/) if you are curious about the other
parts of the library.
The new random number library can be obtained by including <random>. The library is
divided into two categories of objects:
31
1. Engines: Engines are random number generators based on some algorithm. There are
different random number generator algorithms with different characteristics, so there
are different engines.
2. Distributions: Maps numbers generated from an engine into a specific range following
some statistical distribution (uniform distribution, normal distribution, etc.).
To generate a random number in some range with statistical distribution properties, an engine
and distribution must be combined together. As an improved replacement for rand(), the
Mersenne twister engine and a uniform integer distribution works well:
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
int main()
{
// Random number engine that generates non-deterministic random numbers.
// It does not need to be seeded.
std::random_device randDevice;
// Mersenne twister engine is faster than std::random_device and produces very good
// results suitable for most general purpose applications that need random numbers.
// You may need a different engine for a specific scientific domain. It is not
// suitable for cryptography, for example. Constructing/copying the engine is
// slow, so it should be constructed once and passed by reference.
std::mt19937 mt(seed);
// Distribution basically maps numbers generated from an engine into a specific range
// following some statistical distribution (uniform, normal, etc.).
// Uniformly generate random int numbers in range 2-12.
std::uniform_int_distribution<int> dist(2, 12);
11 11 6 7 2 12 10 5 11 5 11 7 3 7 11 6 9 7 3 5
Press any key to continue . . .
32
If you want to generate random floating-point numbers, then use
std::uniform_real_distribution:
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
int main()
{
std::random_device randDevice;
auto seed = randDevice();
std::mt19937 mt(seed);
Before shuffle: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
After shuffle: 14 10 6 7 13 15 4 9 11 17 5 1 18 12 0 19 3 8 16 2
Press any key to continue . . .
21 LAMBDA FUNCTIONS
33
Lambda functions (sometimes just called lambdas) are functions you define without giving
them a name; they are also referred to as anonymous function objects. They are particularly
useful when you need to provide a function object as an argument to another function, and you
only need the function object once. Behind the scenes, the compiler will generate a real
function object for you (that is, a class definition). Therefore, lambdas can also maintain state,
just like a function object. The general syntax of a lambda looks like this:
1. Capture List: Specifies the local variables that are in visible scope of the lambda function
definition that are to be captured for use in the lambda function body. Local variables
outside the lambda function body that are not captured cannot be used in the lambda
function body. If we think of the lambda function as a real function object, then the
capture list corresponds to the state maintained by the function object. Note that you
do not need to capture global variables.
a. [a, &b]: Captures the local variable a by value and the local variable b by
reference.
b. [this]: Captures the this pointer by value.
c. [&]: Captures all local variables in visible scope to the lambda by reference.
d. [=]: Captures all local variables in visible scope to the lambda by value.
e. [&, a, b]: Captures all local variables in visible scope to the lambda by
reference, except a and b are captured by value.
f. [=, &a, &b]: Captures all local variables in visible scope to the lambda by
value, except a and b are captured by reference.
2. Parameter List: The lambda function parameters.
3. Optional Keywords: Optionally, either mutable or noexcept keywords.
a. mutable: The lambda can modify the captured variables. When thinking of the
generated function object class that is generated, this means the operator() is not
const, whereas it defaults to const if mutable is not specified.
b. noexcept: The lambda never throws an exception.
4. -> Return Type: The return type of the lambda. Note that if the lambda has no return
statement or if the lambda body is exactly a single return statement (i.e., { return …; }),
then the return type can be deduced and it does not have to be explicitly specified.
Note that VS13 does not seem to follow this rule and will try to deduce the return type
even when the body is more than a single return statement.
5. Body: Lambda function body.
The choice between capturing by value or reference is the same as passing a parameter to a
function by value or reference. You pass by reference if you need to modify the object in the
function or it is expensive to copy; otherwise, pass by value.
#include <iostream>
#include <vector>
34
#include <algorithm>
#include <random>
int main()
{
std::random_device randDevice;
auto seed = randDevice();
std::mt19937 mt(seed);
std::vector<int> v(20);
// Print the vector and compute the average. Capture average by reference so
// we can modify it.
float average = 0.0f;
std::for_each(std::begin(v), std::end(v), [&average](int x)
{
average += x;
std::cout << x << " ";
});
std::cout << std::endl;
average /= (float)v.size();
std::cout << "The average = " << average << std::endl;
35
std::cout << std::endl;
}
10 5 12 18 16 14 9 12 12 14 19 2 11 1 5 15 14 19 9 19
The average = 11.8
Even number count = 10
10 9 11 9 12 12 12 14 14 14 5 5 15 16 18 2 19 1 19 19
Press any key to continue . . .
22 MOVE SEMANTICS
Move semantics are a significant optimization C++11 has introduced. One immediate benefit is
that the STL containers and algorithms have been implemented using move semantics, so
existing code that uses these features get a performance boost when compiling with a C++11
compiler with no code changes.
In C++98, when we write a = b, we have copying either via a copy constructor or an assignment
operator. Depending on the types involved, we may need to do a deep copy instead of a
shallow copy. For objects that require a lot of memory, we are taught in C++98 to avoid copies.
In particular, we never would return a container (e.g., std::vector) or other large (memory-
wise) object from a function. If you have a function that needs to output a large object, you
would do so through a reference parameter:
36
To make things more concrete, let’s work with the DynamicArray class we made in §8, but
where we have added some logging statements to the destructor, copy constructor and copy
assignment operator. Consider the following program written in debug mode:
return result;
}
int main()
{
DynamicArray<int> a1(5);
Debug Output
Copy constructor
~DynamicArray()
Copy assignment
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
We see that the natural syntax (a1 = GetArray(10);) has the disadvantage of two extra
copies. First, the local variable result is copied into an unnamed temporary variable in
main() via the copy constructor. Then result is destroyed (because it goes out of scope). Then
this unnamed temporary is copied into a1 via the assignment operator. The unnamed
temporary is destroyed and a1 is destroyed when main() exits.
Before we continue, let us point out that one of these copies can be optimized out by
the compiler. If you rerun the program in release mode, then the output is improved:
Release Output
Copy assignment
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
37
In this case, the code is optimized so that result is copy assigned directly into a1. Then result
is destroyed after the assignment, and a1 is destroyed when main() terminates. However, for
the rest of this discussion we will show the debug output because optimizations can vary from
compiler to compiler, and other factors can influence when optimizations are allowed.
Even in the release mode case, we still construct result, fill it, copy it into a1, and then
destroy result. The key idea is that result is going away after we copy it over to a1. This
seems very inefficient. If result is going away, why do the expensive copy operation? Why
not just move the resources of result into a1? This is where move semantics come in.
We basically want to say, if we are constructing an object or doing an assignment, and
the right-hand-side is a temporary (or rvalue), then we do not need to do a copy, and we can
just safely move the resources over to the left-hand-side. However, this raises the question of
how do we distinguish between a temporary and not a temporary in code?
22.2 RVALUES
int main()
{
int x = 2; // x is an lvalue
int&& r1 = x * 32;
//int&& r2 = x; // error: an rvalue reference cannot be bound to an lvalue
int&& r3 = foo(x);
So note that an rvalue reference itself is not an rvalue. So what do rvalue references give us?
Basically, they give a type to temporaries allowing us to distinguish temporaries in code. We
know that temporaries are about to disappear, so it would be safe to move their resources into
another object. By having a type referring to rvalues, we can write overloaded constructors
and assignment operators that take rvalues, which move resources rather than copy it. Thus
move semantics will be invoked when working with rvalues, which is what we want.
38
The move versions will take parameters of type rvalue references so that they will only be
invoked for temporaries where it is safe to move their resources into another object. The
program below adds a move constructor and move assignment operator to the DynamicArray
class (pay close attention to the comments for these methods):
#include <iostream>
#include <string>
template<typename T>
class DynamicArray
{
public:
explicit DynamicArray(int count)
{
mCount = count;
mData = new T[mCount];
}
DynamicArray(std::initializer_list<T> l)
{
mCount = l.size();
mData = new T[mCount];
int i = 0;
for(auto it = std::begin(l); it != std::end(l); ++it)
mData[i++] = *it;
}
// Copy constructor.
DynamicArray(const DynamicArray& rhs)
{
std::cout << "Copy constructor" << std::endl;
mCount = rhs.mCount;
mData = new T[mCount];
for(int i = 0; i < mCount; ++i)
mData[i] = rhs.mData[i];
}
// Move constructor.
// 1. Do not pass by const ref because we need to modify rhs.
// 2. Move constructor should not throw an exception because
// it is not doing anything risky like allocating resources.
DynamicArray(DynamicArray&& rhs) :
// "Steal" the resources from rhs. The key idea here is that moving the
// mData pointer is much faster than the copy constructor where we allocate
// new memory and copy each element over.
mCount(rhs.mCount),
mData(rhs.mData)
{
std::cout << "Move constructor" << std::endl;
39
rhs.mData = nullptr;
}
~DynamicArray()
{
std::cout << "~DynamicArray()" << std::endl;
delete[] mData;
mData = nullptr;
}
// Copy assignment.
DynamicArray& operator=(const DynamicArray& rhs)
{
std::cout << "Copy assignment" << std::endl;
if(&rhs == this)
return *this;
delete[] mData;
mCount = rhs.mCount;
mData = new T[mCount];
for(int i = 0; i < mCount; ++i)
mData[i] = rhs.mData[i];
return *this;
}
if(&rhs == this)
return *this;
delete[] mData;
// "Steal" the resources from rhs. The key idea here is that moving the
// mData pointer is much faster than the copy assignment operator where we
// allocate new memory and copy each element over.
mCount = rhs.mCount;
mData = rhs.mData;
return *this;
}
40
const T& operator[](int index)const { return mData[index]; }
private:
int mCount;
T* mData;
};
return result;
}
int main()
{
// Move constructor.
DynamicArray<int> a1 = GetArray(5);
DynamicArray<int> a2 = GetArray(10);
DynamicArray<int> a3 = GetArray(20);
// Move assignment.
a1 = GetArray(8);
// Copy ctor
DynamicArray<int> a4(a1);
// Copy assignment;
a1 = a2;
}
Debug Output
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Move assignment
~DynamicArray()
Copy constructor
Copy assignment
~DynamicArray()
~DynamicArray()
~DynamicArray()
~DynamicArray()
41
Press any key to continue . . .
Release Output
Move assignment
~DynamicArray()
Copy constructor
Copy assignment
~DynamicArray()
~DynamicArray()
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
A couple remarks:
1. The move constructor/assignment operator steals the resources from the right-hand-
side argument. This is safe because we know the right-hand-side is a temporary
(rvalue), so it is going to be destroyed afterwards.
2. The destructor of the temporary will still execute, so it is important to “null” out the
resources of the temporary so that the destructor can still execute safely.
3. Because we modify the temporary, it must not be const.
DynamicArray<int> a1 = GetArray(5);
DynamicArray<int> a2 = GetArray(10);
DynamicArray<int> a3 = GetArray(20);
do not invoke the move constructor at all (it does invoke the DynamicArray(int count)
constructor). The compiler essentially rewrote the code not to return a temporary, but to
construct and fill a1, a2, and a3 directly.
With rvalue references and move constructors and move assignment operator, we can
implement move semantics safely to avoid excessive copying of temporaries. By safe, we mean
that we only move with rvalues (temporaries about to be destroyed), so there is no danger of
moving the resources of an object and then trying to access the resources of the moved object
later whose resources have been moved.
However, sometimes we want to use move semantics for performance even when
temporaries are not involved as a manual optimization. In this case, the programmer must be
careful, but there is nothing inherently wrong with this. In order to force a move, we need the
compiler to think that the object is an rvalue so that the move constructor or assignment
42
operator is called instead of the copy versions. We can “cast” an object to an rvalue by using
the std::move function (<utility>):
int main()
{
// Move constructor.
DynamicArray<int> a1 = GetArray(5);
DynamicArray<int> a2 = GetArray(10);
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Move assignment
a2.Count = 0
a1 = 0 1 2 3 4 5 6 7 8 9
a2 =
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
43
void PrintArray(const std::string& name, const DynamicArray<int>& v)
{
std::cout << name;
for(int i = 0; i < v.Count(); ++i)
std::cout << v[i] << " ";
std::cout << std::endl;
}
int main()
{
// Move constructor.
DynamicArray<int> a1 = GetArray(5);
DynamicArray<int> a2 = GetArray(10);
MySwap(a1, a2);
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Copy constructor
Copy assignment
Copy assignment
~DynamicArray()
a1 = 0 1 2 3 4 5 6 7 8 9
a2 = 0 1 2 3 4
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
Ouch! There is one copy constructor and two copy assignments. There are no rvalues here,
only lvalues, so copies will be used instead of moving. However, we really do not want to copy,
but just exchange resources, so moving would be ideal and much more efficient:
44
}
int main()
{
// Move constructor.
DynamicArray<int> a1 = GetArray(5);
DynamicArray<int> a2 = GetArray(10);
MySwap(a1, a2);
Move constructor
~DynamicArray()
Move constructor
~DynamicArray()
Move constructor
Move assignment
Move assignment
~DynamicArray()
a1 = 0 1 2 3 4 5 6 7 8 9
a2 = 0 1 2 3 4
~DynamicArray()
~DynamicArray()
Press any key to continue . . .
This is much better, only move operators are taking place in the swap, making it very efficient.
Our DynamicArray class only had “simple” data members (an integer and a pointer):
template<typename T>
class DynamicArray
{
[...]
private:
int mCount;
T* mData;
};
This made it straightforward to steal the resources in the move constructor (and the move
assignment operator which is similar):
DynamicArray(DynamicArray&& rhs) :
mCount(rhs.mCount),
mData(rhs.mData)
45
{
rhs.mCount = 0;
rhs.mData = nullptr;
}
Let us say we added a std::string to the DynamicArray so that the array could have a
name:
template<typename T>
class DynamicArray
{
[...]
private:
int mCount;
T* mData;
std::string mName;
};
We also note that std::string has move operations defined for it. How would the move
constructor look? At first, you might think it is just:
DynamicArray(DynamicArray&& rhs) :
mCount(rhs.mCount),
mData(rhs.mData),
mName(rhs.mName)
{
rhs.mCount = 0;
rhs.mData = nullptr;
rhs.mName = "";// ???
}
The problem here is that it is not as straight forward to steal a user-defined type like
std::string as it is to steal a pointer to a resource. This code is not “stealing” mName from
rhs because we are in fact invoking the copy constructor of std::string with the code
mName(rhs.mName). This happens because rhs.mName is not an rvalue, so the move
constructor is not invoked. What is unintuitive about this is that it might look like an rvalue
because rhs is an rvalue reference (DynamicArray&&). But remember from §22.2 that an
rvalue reference itself is not an rvalue; after all, rhs has a name, which allows us to take the
address of it.
So what we need to do is cast rhs.mName to an rvalue, so that the std::string move
constructor is called, and the string resource is correctly moved:
DynamicArray(DynamicArray&& rhs) :
mCount(rhs.mCount),
mData(rhs.mData),
mName(std::move(rhs.mName))
{
rhs.mCount = 0;
rhs.mData = nullptr;
}
46
A similar thing needs to be done for the move assignment operator.
Therefore, if you have a class C that contains instances of moveable user-defined types,
then the move operations of C should be implemented in terms of the move operations of the
user-defined types. Any built-in type is automatically considered moveable.
1. the class does not define a copy constructor, copy assignment operator, or destructor
2. every non-static member of the class is moveable. Built-in types are always moveable
and user-defined types are moveable if they define a move constructor and move
assignment operator.
Note: If no move operations are defined in a class, the copy operations are used for rvalues, just like they would
have been used in C++98.
27 SMART POINTERS
C++ programmers have been taught to practice vigilance when allocating and deleting memory,
otherwise memory leaks or dangling pointers can occur. Moreover, extra care must be taken
when allocating memory in a function where an exception can be thrown, as the code must
make sure the memory is destroyed before the stack unwinding begins. Finally, given a raw
pointer, it is not always obvious who “owns” the pointer; by ownership, we mean who is
responsible for deleting it. Smart pointers are a tool to help ease these issues. By combining
the STL containers along with smart pointers, you almost never have to invoke the new or
delete operators ever again. The standard library provides three smart pointer classes:
1. std::unique_ptr
2. std::shared_ptr
3. std::weak_ptr
The one you choose depends on the desired properties. To use these smart pointer classes,
#include <memory>.
27.1 UNIQUE_PTR
A smart pointer is a lightweight class that wraps a raw or naked pointer to dynamic memory.
std::unique_ptr should be your default smart pointer of choice. It is the most lightweight
compared to the other smart pointers, giving very little overhead compared to raw pointers. A
unique_ptr is created by specifying the type of pointer you want via template arguments and
constructing it with a new statement or using the make_unique function. Note that VS13
47
supports the make_unique function, but technically it is part of C++14 (a future revision), not
C++11. We prefer make_unique because it gets rid of the new statement altogether.
#include <iostream>
#include <string>
#include <memory>
int main()
{
// Creating using std::make_unique
std::unique_ptr<std::string> p = std::make_unique<std::string>("Hello");
std::unique_ptr<std::string> q = std::make_unique<std::string>("World");
01234
*p = Hello
48
*q = World
*r = 5
First occurance of l in *p is at index: 2
p != q
Press any key to continue . . .
As you can see from the sample program, unique_ptr overloads the usual pointer operators
so that you can use them just like raw pointers. If necessary, you can get a copy of the raw
pointer the unique pointer maintains internally using the get() function.
So far we have shown that you use work with a unique_ptr much the way you would
with a raw pointer. So how does this benefit us? You may have noticed in the above sample
program that we did not delete any of the pointers. This is not a memory leak. When a
unique_ptr goes out of scope, its destructor will be called which will delete the underlying
pointer. This solves two of the problems with raw pointers: 1) remembering to call delete,
and 2) freeing allocated memory when an exception is thrown. The reason it fixes the second
problem is that when an exception is thrown in a function, the destructors of all constructed
objects local to the function are guaranteed to execute before stack unwinding, so the
destructor of any smart pointer will execute, thereby freeing the dynamic memory. The
following example illustrates:
#include <iostream>
#include <string>
#include <memory>
class C
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C(){ std::cout << "C::~C()" << std::endl; }
};
void f()
{
std::unique_ptr<C> p(new C());
throw std::exception("error");
}
int main()
{
try
{
f();
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
}
C::C()
49
C::~C()
error
Press any key to continue . . .
So notice that the destructor is called, even with the exception being thrown. In contrast, if you
used a raw pointer in f then, the destructor is not called.
void f()
{
C* c = new C();
throw std::exception("error");
// memory leak!
}
Even without the exception, there would still be a memory leak with the raw pointer because
there is no “delete c;” statement.
We now address that ownership issue. The key property of a unique_ptr is that there
is exactly one owner of the object pointed to. By having only one owner, it is clear who
responsible for deleting the pointer. Because there is only one owner for a unique_ptr, you
cannot copy it:
std::unique_ptr<C> p = std::make_unique<C>();
std::unique_ptr<C> q = p; // error, assignment operator deleted
If you could copy unique pointers, then you would have two unique pointers pointing to the
same object. You can, however, transfer ownership:
std::unique_ptr<C> p = std::make_unique<C>();
The release() method returns the pointer so another unique_ptr can claim it, and it sets
the state of the calling unique_ptr to null. Another way to transfer ownership is by moving:
std::unique_ptr<C> p = std::make_unique<C>();
When we move q to r, r “steals” ownership of the underlying pointer and q points to null. In
particular, the ability to transfer ownership means we can store unique_ptrs in containers:
#include <iostream>
50
#include <string>
#include <memory>
#include <vector>
class C
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C(){ std::cout << "C::~C()" << std::endl; }
};
int main()
{
std::unique_ptr<C> p = std::make_unique<C>();
std::unique_ptr<C> q = std::make_unique<C>();
std::vector<std::unique_ptr<C>> v;
// explicit move
v.push_back(std::move(q));
}
C::C()
C::C()
C::C()
C::~C()
C::~C()
C::~C()
Press any key to continue . . .
Observe that the destructors are called because the destructors are called for all the
unique_ptrs when the vector goes out of scope. Using a container of raw pointers would
require iterating over the container and calling delete for each element before the vector
went out of scope.
Due to move semantics, a unique_ptr can also be returned from a function. This is
because the function will evaluate to an rvalue, and so the move constructor or move
assignment operator will be called to transfer ownership rather than make copies:
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class C
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C(){ std::cout << "C::~C()" << std::endl; }
51
};
std::unique_ptr<C> f()
{
std::unique_ptr<C> p = std::make_unique<C>();
return p;
}
int main()
{
std::unique_ptr<C> p = f();
}
C::C()
C::~C()
Press any key to continue . . .
Often you will have a class that with contains a unique_ptr (you only want one owner
of the memory) but other parts of the application need to refer to the object pointed to by the
unique_ptr. Ideally, other code would use a C++ reference to refer to the object. However,
sometimes null might be a valid value; in this case, a raw pointer works. You can get the
underlying raw pointer from a unique_ptr with the get() method.
int main()
{
std::unique_ptr<C> p = std::make_unique<C>();
C* rawPtr = p.get();
// use rawPtr, but rawPtr does not own the object pointed to,
// so do not call delete on it.
}
Also note that if the unique_ptr goes out of scope, the object pointed to will be destroyed.
Any pointers or references that still refer to that object will refer to invalid memory, so use
caution.
27.2 SHARED_PTR
We will not go into as much detail with shared pointers as we did with unique pointers because
shared pointers should be avoided unless you really need their characteristics. The difference
between shared_ptr and unique_ptr has to do with ownership. In contrast to unique
pointers, multiple shared pointers can point to the same object. The dynamic memory is
deleted when the last shared pointer pointing to it is destroyed. To implement this, shared
pointers have the extra overhead of tracking how many shared pointers are pointing to a
particular object; this is called reference counting. In addition, there is overhead to make this
play safe with threads. All this bookkeeping means shared_ptrs are not as lightweight as
unique_ptr. The following example illustrates the basics of shared_ptr:
52
#include <iostream>
#include <string>
#include <memory>
class C
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C(){ std::cout << "C::~C()" << std::endl; }
};
std::shared_ptr<C> f()
{
// Creating using std::make_shared
std::shared_ptr<C> p = std::make_shared<C>();
// Get the number of pointers referring to the same object with use_count().
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
std::shared_ptr<C> q = p;
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
g(p);
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
return p;
}
int main()
{
std::shared_ptr<C> p = f();
std::cout << "Ref count in main(): " << p.use_count() << std::endl;
}
C::C()
Ref count in f(): 1
Ref count in f(): 2
Ref count in g(): 3
Ref count in f(): 2
Ref count in main(): 1
C::~C()
Press any key to continue . . .
Observe how the reference count increases and decreases, and note how the object is not
destroyed until the last shared_ptr to it is destroyed.
53
27.3 WEAK_PTR
#include <iostream>
#include <string>
#include <memory>
class C
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C(){ std::cout << "C::~C()" << std::endl; }
};
std::shared_ptr<C> f()
{
// Creating using std::make_shared
std::shared_ptr<C> p = std::make_shared<C>();
// Get the number of pointers referring to the same object with use_count().
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
std::weak_ptr<C> q = p;
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
g(p);
std::cout << "Ref count in f(): " << p.use_count() << std::endl;
return p;
}
int main()
{
// p points to a deleted object!
std::weak_ptr<C> p = f();
std::cout << "Ref count in main(): " << p.use_count() << std::endl;
}
C::C()
Ref count in f(): 1
Ref count in f(): 1
Ref count in g(): 1
Ref count in f(): 1
54
C::~C()
Ref count in main(): 0
Press any key to continue . . .
Observe how the weak_ptr does not affect the reference count. Also note that the object is
now destroyed when f() ends, as the only shared pointer to it goes out of scope. The
returned weak_ptr from f() now points to a deleted object, which would cause an error if we
tried to access that deleted memory.
28 STD::BIND
Sometimes we have a function of several parameters and we want to fix some of the
parameters. For example, suppose we have a mathematics function f of two variables and we
want to look at the graph when we fix y = 2. One solution would be just to write a new
function g that calls f with y = 2:
float g(float x)
{
f(x, 2.0f);
}
However, rather than defining a new function we can adapt the original function f using
std::bind:
#include <iostream>
#include <functional> // std::bind
int main()
{
// Placeholders denote the parameters of the new function, all other
// values are the fixed values.
// This defines g(_1) = f(_1, 2).
auto g = std::bind(f, std::placeholders::_1, 2.0f);
55
z1 = 29
z2 = 29
Press any key to continue . . .
We can also reorder parameters using std::bind. For example, if we have a function:
then
struct Person
{
Person(const std::string name, int age) :
Name(name),
Age(age)
{}
std::string Name;
int Age = 0;
};
class Database
{
public:
std::vector<std::unique_ptr<Person>> Data;
void Sort()
{
std::sort(std::begin(Data), std::end(Data), &Database::CompareAge);
}
private:
bool CompareAge(std::unique_ptr<Person>& x, std::unique_ptr<Person>& y)
{
return x->Age < y->Age;
}
};
The problem is that CompareAge is not really a binary predicate, as there are actually three
parameters because of the implicit this pointer. Thus if we try to sort with this, we will get:
56
However, we can use std::bind to get a new function object that only has two parameters by
fixing the this parameter:
void Sort()
{
// Placeholders are the parameters of the new function, all other
// values are the fixed values.
auto f = std::bind(&Database::CompareAge, this,
std::placeholders::_1, std::placeholders::_2);
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <algorithm> // std::sort
#include <functional> // std::bind
struct Person
{
Person(const std::string name, int age) :
Name(name),
Age(age)
{}
std::string Name;
int Age = 0;
};
class Database
{
public:
std::vector<std::unique_ptr<Person>> Data;
void Sort()
{
// Placeholders are the parameters of the new function, all other
// values are the fixed values.
auto f = std::bind(&Database::CompareAge, this,
std::placeholders::_1, std::placeholders::_2);
private:
bool CompareAge(std::unique_ptr<Person>& x, std::unique_ptr<Person>& y)
{
return x->Age < y->Age;
}
};
57
int main()
{
Database db;
db.Data.push_back(std::make_unique<Person>("A", 32));
db.Data.push_back(std::make_unique<Person>("B", 64));
db.Data.push_back(std::make_unique<Person>("C", 18));
db.Data.push_back(std::make_unique<Person>("D", 55));
db.Data.push_back(std::make_unique<Person>("E", 29));
db.Data.push_back(std::make_unique<Person>("F", 5));
db.Data.push_back(std::make_unique<Person>("G", 18));
db.Data.push_back(std::make_unique<Person>("H", 19));
db.Sort();
for(auto& p : db.Data)
std::cout << p->Name << ", " << p->Age << std::endl;
}
F, 5
C, 18
G, 18
H, 19
E, 29
A, 32
D, 55
B, 64
Press any key to continue . . .
#include <iostream>
#include <string>
#include <sstream>
return oss.str();
}
58
int x;
iss >> x;
return x;
}
int main()
{
std::string x1 = ToString(7);
std::string x2 = ToString(3.14f);
int x3 = ToInt("5");
x1 = 7
x2 = 3.14
x3 = 5
Press any key to continue . . .
In the <string> header file, C++11 adds new functions for converting between string and the
built-in numeric types:
#include <iostream>
#include <string>
int main()
{
std::string s1 = std::to_string(7); // int overload
std::string s2 = std::to_string(3.14f);// float overload
std::string s3 = std::to_string(1.23); // double overload
std::string s4 = std::to_string(12ul); // unsigned long overload
// wstring variation
std::wstring w1 = std::to_wstring(7); // int overload
std::wstring w2 = std::to_wstring(3.14f);// float overload
std::wstring w3 = std::to_wstring(1.23); // double overload
std::wstring w4 = std::to_wstring(12ul); // unsigned long overload
59
std::wcout << "w2 = " << w2 << std::endl;
std::wcout << "w3 = " << w3 << std::endl;
std::wcout << "w4 = " << w4 << std::endl;
s1 = 7
s2 = 3.140000
s3 = 1.230000
s4 = 12
w1 = 7
w2 = 3.140000
w3 = 1.230000
w4 = 12
x1 = 5
x2 = 12.345
x3 = 1.98
x4 = 12
Press any key to continue . . .
30 NEW CONTAINERS
We merely point out new containers that C++11 has added. See the linked references for the
details.
[Stroustrup14] Stroustrup, Bjarne, The C++ Programming Language, Fourth Edition, Addison-
Wesley, 2014.
60
[Lippman13] Lippman, Stanley B., Joseée Lajoie, and Barbara E. Moo, C++ Primer, Fifth Edition,
Addison-Wesley, 2013.
61