Blog moved!
I'm in the process off moving my blog to headerphile.com. I will update and improve each post in order to make then easier to read. I hope you will like my new blog.
Click here to visit headerphile.com
Programming C++11 and SDL2
søndag 21. september 2014
torsdag 11. september 2014
[ C++11 - Part 4 ] STL - iterators
Standard Template Library
A major part of C++ is the Standard Template Library ( STL ). It is quite large and it contains both containers ( like
vector
, stack
and maps
) with a lot of functionality for using these. In this post, we'll look at one part of the STL
, iterator, and in the following post we'll look at other parts of STL
like the algorithms library Iterators
Iterators can be looked at like pointers. They reference an item in a data structure( including
string
). They can also be used with streams. The iterators enables us to iterate through them using various operations like ++
. But we need different types of iterators because data structures can often be iterated in different ways. Some ways are efficient on some containers while they might be inefficient or even impossible on other containers.
Let's take a look at the different type of
iterator
s.Output iterators
- Very restricted
- Can only be written to ( not read )
- Can only move forward after writing
- Example :
std::cout
Input iterators
- Counterpart of output iterators
- Can only be read ( not written to )
- Move forward after reading
- Example :
std::cin
Forward iterator
- Can do everything
input iterator
can do - Can also move forward at any time
- Example :
std::forward_list
Bidirectional iterator
- Can do everything
forward iterator
- Can also move backwards at any time
- Example
std::list
Random access iterator
- Can do everything
bidirectional iterator
can do - Also supports random access (
[]
) - Example :
std::vector
andstd::array
The properties of the various iterators here are the bare minimum of the "pure" version of the
iterator
s. Some implementations might support more operations.Note that
forward iterator
, bidirectional iterator
and random access iterator
does not implement output iterators
. This means that they can't, by definition, write to the object they refer to. We'll get back to this later. Let's start by looking at the first type of iterator,
output iterator
.Output iterator
As mentioned earlier,
output iterator
is a very limited type of iterator
it is also the only iterator
that can write to the object.You can't go back with an
output iterator
, and you can't iterate over the same range twice. And there is no guarantee that you can write to the same position twice without incrementing the operator. And you can't compare two output iteratrs
either. What you're basically doing is writing into a "black hole"; you're just writing and you can't see what you have written.This might seem very constrictive, there's very little we can do. But these rules only apply in some cases, like when writing to
stream
s, which we'll look at later. You can look at the iterators
that follows everything described above as pure output iterators
Most
iterators
, though implement output iterators
but also has other traits that enables more functionality. We'll look at these later.A simple example
This is a very simple example of the intended usage for
output iterators
:OutputIterator r2;
while ( ... )
{
*r2 = x;
r2++;
}
Or, in other words :- Write
- Move forward
- Write
- Move forward
- ...
A bit more complex example
In
C++
you have streams, which allows you to to either write or read from them using the >>
and <<
operators. Examples of this is cout
( write ) cin
( read ). This makes cout
output operators
( because they output to the terminal. )cout
is of the type ostream
. You also have an iterator
to this type, called ostream_iterator
that can be set to an ostream
Writing to this iterator writes to the ostream
it's an iterator
to. Using this knowledge, we can create an
iterator
to cout
, this enables us to write to the terminal using an iterator
#include < iterator >
#include < iostream >
int main()
{
// Create an ostream_iterator to std::cout.
// The "\n" means we add a line every time we write
std::ostream_iterator< int32_t > it(std::cout, "\n");
*it = 11;
it++;
*it = 44;
it++;
*it = 33;
it++;
}
Output :11The
44
33
ostream_iterator
s are output iterators
. Using cout
like this shows how output iterators
works ; you can only write and the writing has to be sequential. You can newer go back. Once you've written something, you can't "go back" and write something in the position before what you just wrote.Input iterator
The counterpart of
output iterators
is input iterators
. But where output iterators
can only write, input iterators
can read but there is also a small changes in the restrictions :- You can compare two iterators
- Comparing two
iterators
is not guaranteed to work unless at least one of them is past the end ( likestd::end
) - Since you can read, you can also use the
->
operator to access member variables
A simple example
The following example shows the essential use of an
input iterator
:while( ... )
{
std::cout << *pos;
++pos;
}
Or, in other words :- Read
- Move forward
- Read
- Move forward
- ...
A more complex example
In the previous example, we saw that
ostream_iterators
are output iterators
s. Which means we can use them to write to cout
. Similar to this, we have istream_iterator
s that are input operator
s. And where we can use ostream_iterators
with cout
, we can use istream_iterators
with cin
which reads input. Here too we have to work consecutively, we can't go back or skip ahead, we can read input in a similar to when we wrote to
cout
#include <iterator>
#include <iostream>
int main()
{
// ...
std::cout << "Write something " << std::endl;
std::istream_iterator< int > intReader( std::cin );
// readerEnd refers to the end element of the stream
// This is similar to std::end()
std::istream_iterator< int > readerEnd;
while ( intReader != readerEnd )
{
std::cout<<"Write "<< *intReader<< std::endl; std::cout<<"You wrote "<< *intReader<< std::endl; ++intReader;
}
}
Example input :3Output :
2
6
f
Since we've specifiedWrite 3
You wrote 3
2
Write 2
You wrote 2
6
Write 6
You wrote 6
f
int
as the template argument of the input_iterator
, the input_iterator
( intReader
) will become the same as invalid, which in C++
means the iterator
will point to the element past the end
element. This is the same as readerEnd
and the loop will terminate.Forward iterator
If you take a look at the
iterator
overview above, you'll see that the forward iterator
implements output iterator
. This means a forward iterator
can do anything an output iterator
can do but with a few more properties : - Two
forward iterator
s are guaranteed to compare as equal if they refer to the same element - An
input iterator
can, as we saw above, be compared to anotheroutput iterator
, but the operation is only guaranteed to betrue
if one of them is past the end of the container - Example :
Forward_Iterator it1 = beginIterator; Forward_Iterator it2 = beginIterator; // Compare bool ( it1 == it2 );
The compare operation ( it1 == it2
) is not guaranteed to be true for output iterator
s or input iterator
s, but it is for forward iterators- A
forward iterator
can also iterate forward as many times as you like before reading - With
output iterator
s orinput iterator
s we would have to read or write in between each time we iterate forward. - Example :
Forward_Iterator it1 = beginIterator;
// Iterate it1 forward a few steps
++it1;
++it1;
++it1;
// Use (*it1) for something
// ....
If we had used an output iterator
or an input iterator
to do the multiple iterations would have caused the iterators to become invalid.- Finally, a
forward iterator
can iterate over the same range twice. -
output iterator
s orinput iterator
s are intended to only iterate over the same range once (single-pass
) but are intended to be able to do this (multi-pass
)
- Example :
Forward_Iterator it1 = beginIterator;
// Iterate it1 forward a few steps
++it1;
// Use (*it1) for something
// ....
// Go back to beginIterator
it1 = beginIterator;
// Iterate it1 forward a few steps
++it1;
// Use (*it1) for something
// ...
Here we've effectively moved back and continued moving forward, which again would not be possible with code>output iterators or input iterator
s.As you can see, using
forward iterators
is in many ways more convenient than using output iterator
s or input iterator
s, but some object only has very few operations, like the stream
s we looked at earlier. Luckily, most ( if not all ) structures in the
C++11
supports at least the functionality of forward iterator
s. Most of them provide iterators
that has more properties, but this time we'll look at a structure that only provides forward iterators
Forward list
A linked list is a container that works a bit different from the array-like containers. Basically, each element has a pointer to the
next
element and previous
. So that all the elements are chained together using pointers. If you want to go from one element to another, you use the next
pointer ( or the previous
if you want to go back ) This can mean a lot of operations if the list is very large.The objects will not be one contiguous piece of memory like arrays are. This means there are no quick ways to go from the first to the last element. So if we wanted to go from
Node 1
to Node 2
in the above illustration, we would have to go via Node 3
. And if we wanted to go back from Node 3
to Node 1
we would have a problem. There is no pointer that goes back in a forward list
. If you want to get to a previous node, you would need a pointer to an earlier element. The forard_list
itself keeps a pointer to the first element in order to be able to access all elements.A container in like this didn't exist in
C++
until C++11
which has the container forward list.
Iterating forward lists
The
forward list
is a great way of showing how the forward iterators
works. The forward list
can only be iterated forward and the forward iterator
can only iterate forwards. Let's look at an example :
std::forward_list< int32_t > list( { 43, 63, 11, 0, 5 } );
// Get a forward iterator to the first element of list
std::forward_list<int32_t>::iterator it=std::begin(list);
// Print value
for ( int32_t i = 0 ; i < 4 ; ++i )
{
std::cout << (*it) << std::endl;
++it;
}
We are now at the fifth element ( 5
), but say we want to print the previous element ( 0
)? We need to go back to the begin
element and increment like above.// Go back to the first element
it = std::begin( list );
// Iterate forwards to the element before the one above
for ( int32_t i = 0 ; i < 3 ; ++i )
++it;
std::cout << (*it) << std::endl;
Output43
63
11
0
5
0
Forward iterators and input iterator
- Per definition,
forward iterators
does not include the properties ofoutput iterator
s. - Most implementations of
forward iterators
does however support assignment likeoutput iterator
s. These iterator are calledmutable
forward iterators
Bidirectional iterator
The
bidirectional iterator
is very similar to forward iterator
. The only different is that it also supports backwards iteration. And that's all the difference between code>forward iterator and bidirectional iterator
.Linked list
Linked list
In the
forward iterator
section we looked at singly linked list
s which has a pointer to the next element. But most linked list
s also has a pointer to the previous element.These are called doubly linked list
s or simply just linked list
.The container type
doubly linked list
s are identical to singly linked list
s, with the one exception that each Node
now has a pointer to the previous Node
too. The
prev
pointer enables us to iterate backwards, so now we need an iterator that can iterate backwards and forwards. This is where the bidirectional iterator
comes in. An example
The following example is the similar to the example for
forward list
but this time we solve the last problem in a different way. std::list< int32_t > list( { 43, 63, 11, 0, 5 } );
// Get a forward iterator to the first element of list
std::list< int32_t >::iterator it = std::begin( list );
// Print value
for ( int32_t i = 0 ; i < 4 ; ++i )
{
std::cout << (*it) << std::endl;
++it;
}
std::cout << (*it) << std::endl;
We are now at the fifth element ( 5
), but say we want to print the previous element (0
)? We can simply use the --
operator to iterate back;Output
--it;
std::cout << (*it) << std::endl;
43
63
11
0
5
0
As you can see, bidirectional iterators
can iterate back and forth without problems. Now let's look at the final iterator
, bidirectional iterator
.Random access iterator
The final
iterator
is also the one with the most features. It can be used like a pointer. In fact, some implementations of STL
uses pointers as random access iterators
.Here's a list of the features of a
random access iterator
- All the features of
bidirectional itearator
- Random access
- Access items by
[]
operator it[n]
returns the value of the positionit + n
it[n]
is the same as*( it + n )
- Iterator arithmetics
- This means you can add or subtract integer values and iterators
it += n
moves it forwardn
stepsit -= n
moves it backwardn
stepsit + n
returns an iterator to the elementn
steps afterit
n - it
returns an iterator to the elementn
steps beforeit
it - it2
returns the distance betweenit
andit2
- Iterator comparison
- This means you can check which if one
iterator
is before anotheriterator
it1 < it2
returnstrue
ifit1
is beforeit2
it1 > it2
returnstrue
ifit1
is afterit2
it1 >= it2
returnstrue
ifit1
is not beforeit2
it1 >= it2
returnstrue
ifit1
is not afterit2
not it1 < it2
Array-like containers
Array like containers are all containers that are organized as a continuous piece of memory. This means you can predict where the next item is, since it the next item will always be the previous item + the size of each item. Using this knowledge, it's easy to jump forward or backwards. An example of these containers is vector, which I covered in a previous part.
The above example shows how any array-like container (
vector
, array
, queue
, deque
, ... ) is laid out in memory. Everything is in one continuous block which means we always know where the next element is.An example
The following example shows usage of
random access iterators:
As you can see, the
+
operators of random access iterator
s are very useful. We will be using this for the upcoming post about STL algorithm
s.For a full list of my tutorials / posts, click here. Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.
tirsdag 26. august 2014
[ C++11 - Part 3 ] Smart pointers
Memory in C++
One of the major parts of C++11 is the new smart pointers. But why do we need them, and why should you steer away from the old style pointers?
This post is intended both as an introduction, and a reference guide to the smart pointers found in
C++11
. It will cover what they are, how to use them and how they work.Memory allocation in C++
C++ supports
dynamic allocation
( new
/ delete
). It's a way of creating a object whenever you need it. The object will live until you call delete
on it. Otherwise it won't be deleted, not even when it goes out of scope. And if you don't delete it, none will, which in turns causes memory leaks. It might also cause undefined behavior ( which means anything can happen ) if, for instance, you delete something and try to use it afterwards.Smart pointer will help you with these issues and make your code cleaner and better.
Smart pointers
Smart pointers are like regular pointers, but they provide extra functionality such as automatic checks or other safety mechanisms. C++11 introduces three smart pointer types :
- unique_ptr
- Only one
unique_ptr
can point to the object at the time - shared_ptr
- Several
share_ptr
s can point to the object at a time. - Object is released when no
share_ptr
s point to the object - weak_ptr
- Non-owning, needs to be converted into an
shared_ptr
to use the object it points to
Using smart pointers
Once you have created the smart pointer, you can use it as a regular pointer. So operations like
!=
, !
, !
, ->
, *
etc. This is not true for weak_ptr
, but we'll get to that later. You can also use them in
if
s like this : if ( ptr )
this will return true as long as the smart pointer is initialized and is managing a valid object ( meaning you can use the pointer. )We'll now take an in-depth look at the smart pointers, one at the time.
unique_ptr
An
unique_ptr
is the sole owner of an object that it points to. Two unique_ptr
s can also swap objects, but they can never manage the same object. And as with all smart pointers, it takes care of deleting the object. Initialization and allocation
There are two ways of doing this, regular constructor or
make_shared
constructor
The constructor is pretty straight forwards. You just set the template arguments ( in this case
Foo
) and pass an allocated ( new
'ed ) object :std::unique_ptr< Foo> p1(new Foo);
This is pretty straight forwards ; template parameter is the type and you supply an allocated (
new
'ed ) object. make_unique
make_unique
The second alternative is using a function named
make_unique
, this is in most cases better, because it constructs the object and it helps prevent errors. It also follows the naming convention ofshared_ptr
which can be constructed in the same way with the make_shared
method.This is how to use
make_unique
: >std::unique_ptr< Foo > p2 = std::make_unique< Foo > ();
What
make_unique
does is that it creates a new unique_ptr
that manages a new object of the template type ( Foo
in our case. ) It takes the arguments of the make_unique
function itself and passes it to the constructor of the new Foo
. Then the function returns the unique_ptr
it created and the =operator
assigns it to the unique_ptr
we declared ( p2
)This function requires
C++14
, it should have been included in C++11
, a simple oversight lead to it not being included in the standard. But don't worry, it's easy to get this to compile:- On
Linux
/Mac
, you can do this by changing-std=c++11
to-std=c++1y
in your compile command - On
Windows
, newer versionsVisualStudio
should support this without you having to do anything.
Destruction and deallocation
Since the
unique_ptr
is a smart pointer, it handles deallocation automatically. In general, this means that the unique_ptr
calls delete
on the object it holds. This happens when :- The
unique_ptr
is being destroyed. - Happens when the
unique_ptr
goes out of scope. - You set the
unique_ptr
to point to something else
Releasing the object
Releasing the object means that the
unique_ptr
stops managing the object. This means you can let a regular pointer "take over" the object the unique_ptr
was managing. So when the unique_ptr
goes out of scope or is destroyed in any way, the pointer object it was managing wont be affected. Here's an example
Foo* pFoo1 = new Foo();
std::unique_ptr< Foo > fooUPtr( pFoo1);
fooUPtr
now manages pFoo1pFoo1
is unchanged
Foo* pFoo2 = nullptr;
pFoo2 = fooUPtr.release();
But now
fooUPtr
no longer manages the objectpFoo1
points topFoo2
now points to the same object aspFoo1
pFoo1
/pFoo2
needs to be deleted. Since they both point to the same object, we just need to delete one of them : delete pFoo1;
Changing the object being pointed to / reassignment
Changing what the
unique_ptr
points to is quite easy. It can be done in two ways :- Using the = operator
- Using the reset() function
These work int the similar fashion
std::unique_ptr< Foo >( new Foo ) foo1;
foo1 = new Foo();
Andstd::unique_ptr< Foo >( new Foo ) foo2;
foo2.reset( new Foo() );
Both of these properly deallocates the original object managed by
foo1
, calling its destructor in the process. Then the new object returned by new
is assigned to foo1
and foo1
now manages it just like it did with the previous object. Transferring ownership
You can't set two
unique_ptr
s to manage the same object, but you can transfer ownership from one unique_ptr
to another. And you can swap the managed object of two unique_ptr
s. Swapping owenership
Swapping ownership can be done in two ways ; a member function, or the
std::swap()
function.Member function
The member function is very straight forwards :
std::unique_ptr< Foo >( new Foo() ) foo1;
std::unique_ptr< Foo >( new Foo() ) foo2;
foo1.swap( foo2 );
std::swap
std::swap()
is a very simple, but also very useful algorithm defined in the algorithm
header ( until C++11
and utility
since C++11
). It basically swaps two objects of whatever type you pass as arguments. It can be two int
s, two std::string
s or two objects of a class
/struct
or anything else you want. The version used for
unique_ptr
s is just an overload of this that uses the above swap( std::unique_ptr )
member function function internally. Here's how to use it : std::unique_ptr< Foo >( new Foo() ) ;
std::unique_ptr< Foo >( new Foo() ) ;
std::swap(foo1, foo2 );
So in reality, the member function and
swap()
are identical. The result of both of these are as you would expect :foo1
now managesfoo2
foo2
now managesfoo1
Reseting
You can use the
reset()
to reset the unique_ptr
This means that we change what pointer the unique_ptr
manages, deleting the old one. This method can only be used with raw pointers, not smart pointers. Example :
std::unique_ptr< Foo> ptr( new Foo );
foo.reset( new Foo );
What this does
- Crates a new
uniqe_ptr
that manages a newFoo
object - Deletes the old managed
Foo
object and replaces it with a new one
Transfering owenership
You can use the
= operator
to transfer ownership :std::unique_ptr< Foo >(new Foo() ) foo1;
std::unique_ptr< Foo >(new Foo() ) foo2;
foo1 = foo2;
This takes the pointer from foo2
and gives it to foo1
. This means that : foo2
no longer handles any object ( like calling )- This is because only one
unique_ptr
can manage an object at a time - The object that
foo1
was holding is deleted - Because
is going to be managingfoo1
foo2
std::unique_ptr< Foo >(new Foo() ) foo1;
std::unique_ptr< Foo >(new Foo() ) foo2;
foo1.reset( foo2.release() );
Note that this is not the same as swapping two unique_ptr
If we swap, the result would be :
In this case the result is.
foo1
managesfoo2
foo2
managesfoo1
foo1
managesfoo2
foo2
doesn't manage any object
Destroying the object
When you want to destroy the object the
unique_ptr
manages, there are two ways :- Destructor
- This simply means letting the
unique_ptr
go out of scope - The
reset()
method - You can use this with either a
NULL
pointer or another pointer, the managed object will get deleted in both cases
The bool operator
The
bool operator
dictates what happens when used in a boolean
expression like if ()
. It will return true
if the unique_ptr
manages an object. If not, it'll return false
. In other words ; if, and only if, you can use it, it'll return true
, so this is exactly the same as you do when you're checking if a pointer is NULL
or nullptr
The other operators
The other operators works just like regular pointers, so I won't discuss them here.
shared_ptr
shared_ptr
is, as the name implies, a smart pointer that allows sharing. This means you can have several shared_ptr
s that point to the same object. This is not permitted by unique_ptr
as we saw in the previous section.Implementation
The
shared_ptr
is a bit more complex than unique_ptr
. This is because a shared_ptr
needs to keep track of how many other shared_ptr
s are managing this object
, But for unique_ptr
s there will always be only one pointer managing the same resource, so there is no need to keep track of the number of other unique_ptr
s that are managing this object ( because it's always just one! )In order to keep track of this information,
shared_ptr
keeps a pointer to a structure called a control block
. This structure has three member variables :-
shared_ptr
reference count - How many
shared_ptr
s are managing to the object weak_ptr
reference count- How many
weak
_ptr
s are referring to this object - More on
weak
_ptr
s later - A pointer to the object the
shared_ptr
manages
The
shared_ptr
also keeps a pointer to the object it manages. And this is were it gets a little complicated, because this pointer is closely related to the one in the control block. But why? The reason for this is a bit complicated and it has to do with the two ways of creating a shared_ptr
, make_shared
and constructor. I will discuss this in the next section :Initialization
When it comes to
unique_ptr
, the difference between using make_unique
and the regular constructor is slight ( that doesn't mean you shouldn't use make_unique
as often as you can! ) But in a shared_ptr
things are different. As stated above, a
shared_ptr
has two elements : a control block and a pointer to the object it manages. Both of these needs to be allocated, but before we get into that, let's look at how we can create a shared_ptr
: std::shared_ptr p1(new Foo);
std::shared_ptr< Foo > p2 = std::make_shared< Foo > ();
In the first example, we first allocate Foo
using new
before pass it to the shared_ptr
constructor. This means that the share_ptr
has no control over the allocation of Foo
so all it can do is to create the control block and have it point to Foo
. The figure below will show the procedure for when you create a shared_ptr
using constructor :Foo
and ControlBlock allocated in two steps
As you can see, the
control block
and the Foo
needs to be allocated in two steps. First the object, then the control bloc. But if we let
make_shared
handle the allocation of Foo
, it can allocate both the control block
and Foo
in one go. It'll look something like this :Foo
is now part of the FooControlBlock
So
make_shared
creates the object and the control block together in one operation. This makes the operation faster than creating them in two step, but it requires them to be one object, so here Foo
is part of the control block itself. make_shared
is available in C++11
so you can use it without enabling C++14
When not to use make_shared
?
There are two cases when you can't use
make_shared
:- If you are using a pointer that's already created somewhere else
- Using
make_shared
means the object would be re-allocated - Pass the pointer in the constructor instead ( where we passed
new Foo()
in the example above ) - If you don't want to use the default
delete
- You can't specify a custom deleter using
make_shared
- This is a bit complicated, so I won't go into details
Destruction and deallocation
The destructor for
shared_ptr
is also a bit different from unique_ptr
because an unique_ptr
will always be the sole manager of an object ( not other unique_ptr
or shared_ptr
swill be managing it. ) This means it's always safe to delete
, so that's what the unique_ptr
will do.But when it comes to
shared_ptr
s, we can't do that before we make sure that no other shared_ptr
s are managing it. So what we do is that we look on the control block and how many shared_ptr
s are managing it. If this is 0
, we are the last owner and we can safetely delete it. The
weak_ptr
reference count it not checked at this point. I'll get into why in the next section that discusses weak_ptr
and how the relate to shared_ptr
s.Changing the object being pointed to / reassignment
Similar to
unique_ptr
s but here we need to do some extra work. There's two different cases for this ; setting the shared_ptr
to be the same as another shared_ptr
and setting the shared_ptr
to manage a new pointer. In both of these cases, it will decrement the
shared_ptr
reference count in the control block. And if this count reaches 0
it will delete the object being pointed to ( but not necessarily the control block, more on this later. ) Assigning to a different shared_ptr
Assigning a
shared_ptr
to a different shared_ptr
is done using the =operator
.Here's a simple example
// Create shared_ptrs
std::shared_ptr< Foo > ptr1 = std::make_shared< Foo >();
std::shared_ptr< Foo > ptr2 = std::make_shared< Foo >();
// Reassign
ptr2 = ptr1;
Result - The original
ptr1
'sshared_ptr
count is now0
, and the object it manages will be deleted ptr1
andptr2
will now both manage the same object as the originalptr2
with ashared_ptr
count of2
Assigning shared_ptr
to a new object
Assigning a
shared_ptr
to a new object/pointer is done using the reset()
function :Here's a simple example
Result// Create shared_ptrs
std::shared_ptr< Foo > sharedPtr = std::make_shared< Foo >();
Foo* rawPtr = new Foo();
// Reassign
sharedPtr
_ptr.reset( rawPtr );
- The
shared_ptr
reference count forsharedPtr
is decremented as if we were calling the destructor. - The
Foo
objectsharedPtr
was originally manging may get deleted sharedPtr
now manages the objectrawPtr
points to.
As you can see from the examples, you use
opeator=
for reassigning to another shared_ptr
but reset()
for reassigning to a different raw pointer. You can't use them the other way around. This can help prevent bugs by giving an error if the programmer uses the wrong versions. There is a way you can use
operator=
to assign to a new pointer; using make_shared< Foo >
to create a new object : std::shared_ptr< Foo >( new Foo ) foo2;
foo2 = make_shared< Foo >();
This works because make_shared
creates and returns a fully constructed shared_ptr
( just like make_unique
described above ) and the =operator
assigns it just like in the example above.Swapping
The syntax for swapping
shared_ptr
s is the exact same as for swapping two unique_ptr
s : std::shared_ptr< Foo >( new Foo ) foo2;
std::shared_ptr< Foo >( new Foo ) foo2;
Member function :foo1.swap( foo2 );
std::swap :std::swap( foo1, foo2 );
This will, as you would expect, swap both the control block and pointer for both the shared_ptr
. It needs to swap the control block since this is what keeps tracks of the number of references to the pointer so these needs to be consistent. The bool operator
The
bool operator
dictates what happens when used in a boolean
expression like if ()
. It will return true
if the shared_ptr
manages an object. If not, it'll return false
. In other words ; if, and only if, you can use it, it'll return true
, so this is exactly the same as you do when you're checking if a pointer is NULL
or nullptr
This is exactly the same as for
unique_ptr
.The other operators
The other operators, just like with
unique_ptr
, works just like regular pointers, so I won't discuss them here.weak_ptr
As mentioned in the introduction,
weak_ptr
doesn't actually manage a pointer. It just holds a non-owning ( "weak" ) reference
to an object that is managed by a shared_ptr
. It also keeps a pointer to the control block ( the exact same as the one in the shared_ptr
who manages the object. This means it has to be created from a shared_ptr
so that it can get a pointer to the control block. wrak_ptr
and the control block
The control block, as we saw in the previous section, keeps a count of both
shared_ptr
and weak_ptr
who's using the object. We also saw that the object will get deleted if the count of shared_ptr
s using the object is 0
regardless of how what the weak_ptr
s count is. This is part of the point of weak_ptr
; it is not supposed to keep objects alive except for in situations we explicitly tell it to. But even though the managed object will get deleted if the count of
shared_ptr
s is 0, the control block will remain intact. The control block will only be deleted if both the conut of shared_ptr
and weak_ptr
uses. This is because the weak_ptr
uses the control block to check if the object is alive. Creating a weak_ptr
There are two ways of creating a
weak_ptr
from a shared_ptr
: constructor and =operator
. This is very straight forwards : std::shared_ptr< int > sharedPtr = std::make_shared( 42 );
// 1. Constructor
std::weak_ptr< int > weakPtr1( sharedPtr );
// 2. = operator
std::weak_ptr< int > weakPtr2 = sharedPtr;
You can also create one from another weak_ptr
:// 3. Constuctor - weak_ptr
std::weak_ptr< Foo > weakPtr3( weakPtr1 );
// 4. = operator - weak_prt
std::weak_ptr< Foo > weakPtr4 = weakPtr2;
/code>
All of these will set up the pointer and the control block and increment weak count by 1. Creating a
weak_ptr
from a raw pointer won't work simply because it isn't designed to be managing a pointer by itself. And you can't use them with unique_ptr
s because unique_ptr
s are supposed to be the sole owner. Reseting a weak_ptr
The object a
weak_ptr
can be reset ( so that it no longer references any object ) using the destructor or the reset()
function.
// Set up a weak_ptr
std::shared_ptr< Foo > sharedPtr = std::make_shared< Foo >();
std::weak_ptr< Foo > weakPtr = sharedPtr;
// Reset it
weakPtr.reset()
Using a weak_ptr
weak_ptr
has the function lock()
. What this function does is that it makes a shared_ptr
of itself. This shared_ptr
will work exactly as any other shared_ptr
s maintaining the object. It will increase the shared_ptr
count, so this shared_ptr
will keep the object alive. If the object has been deleted, it will still return a
shared_ptr
. But this shared_ptr
doesn't point to anything, it's essentially a nullptr
. It will return false
if you use it in a if
or loop condition, just like a regular shared_ptr
or unique_ptr
that doesn't manage an object.This function is the crucial part of the
weak_ptr
. It enables us to keep weak_ptr
s that refers to object maintained by a shared_ptr
without preventing it from being deleted and we can still use it safely when we want. So let's look at an example :
Usage 1
Here everything works as intended. The
weak_ptr
refers to sharedPtr
which has a valid object inside this scope. lock()
returns ashared_ptr
that manages the same object assharedPtr
- The returned
shared_ptr
is valid so it enters the if - While in this if, the
shared_ptr
,fromWeakPtr1
keeps the object alive, even if all othershared_ptr
should be destroyed in other threads in the meantime
Usage 2
Our
shared_ptr
s has gone out of scope ( both the original and the one we created from the weak_ptr
and the object has been deleted. But the weak_ptr
, weakPtr
still exists as it was declared out of the scope. So we try to use it again :- We create our
shared_ptr
from theweak_ptr
a - The
shared
is created, but since the original object was deleted, this is essentially a nullptr - The
shared_ptr
returns false and it doesn't enter the if.
Leaving the scope
Finally we leave the scope and we delete
weakPtr
. Now the weak_ptr
count will be 0
and the control block will be deleted ( shared_ptr
count was already 0
)Releasing the object
A
weak_ptr
can't release the object because it doesn't have control over it, and the object might be NULL
. For this reason, weak_ptr
does not have a release()
function. The closest thing is the reset()
function described above.Swapping
The syntax for swapping
weak_ptr
s is the exact same as for swapping two shared_ptr
s and code>unique_ptrs: std::weak_ptr< Foo >( new Foo ) foo2;
std::weak_ptr< Foo >( new Foo ) foo2;
Member function :foo1.swap( foo2 );
std::swap :std::swap( foo1, foo2 );
This will, as you would expect, swap both the control block and pointer for both the
weak_ptr
s. It needs to swap the control block since this is what keeps tracks of the number of references to the pointer so these needs to be consistent.Changing the object being pointed to / reassignment
Similar to
shared_ptr
s but with a few differences :- Since we are not managing the object, we don't need to worry about deleting it.
- It might already have been deleted at this point, but this will not cause the opeartion to fail
- Decrement count for
weak_ptr
, notshared_ptr
- If
weak_ptr
count reaches0
and the count forshared_ptr
is also 0, we delete the control block - Now we can set the pointer to the object and control block
- Both of these will already have been created by a
shared_ptr
You can create reassign a
weak_ptr
to both a shared_ptr
and another weak_ptr
.For a full list of my tutorials / posts, click here. Feel free to comment if you have anything to say, spot any errors or have any suggestion or questions. I always appreciate getting comments.
onsdag 30. juli 2014
[ SDL2 - Part 11 ] Text styling
Text styles in TTF
In the last post we looked at how to render text. Now let's take this a step further and change the appearance of the font. There are two ways you can change how the font looks. Font style and font outline.
Fon styles
SDL TTF allows you to set the style of the font. It supports the following font styles
- bold
- italic
- underlined
underlined
You can combine these in any way you like. We'll start of with just setting a single font style at a time, and then move on to see how we can apply several of them at once.
Font styles
Settnig font styles in TTF is easy, it just requires a single function. The function let's you set one or more font styles. Let's start off by looking at how to set just one font style
Setting the font style
We can set the font style using the following function
void TTF_SetFontStyle
(
TTF_Font *font,
int style
)
The arguments are :-
TTF_Font *font
- the font to set the style on int style
- the style to set on the font
As you can see, the
style
parameter is an int
and not an enum
. I'll get back to why that is later, but for now let's look at the possible values for style
, these are all self-explanatory so I won't be adding a description.- TTF_STYLE_NORMAL
- TTF_STYLE_BOLD
- TTF_STYLE_ITALIC
- TTF_STYLE_UNDERLINE
TTF_STYLE_STRIKETRHOUGH
Any text you render after setting this font style will have the new effect, but it won't change any text you have written with a different style. So when you set the style to
TTF_STYLE_BOLD
, all text you render from that point and until you set a different style will be bold. And as long as you pass any of the above values to the function, the font will only have the one last style you set.Let's to a simple example
Init TTF and load a font
Any text rendered at this point will be normal with no font stylesTTF_Init();
TTF_Font font = TFF_LoadFont("font.ttf", 16);
Any text rendered at this point will be boldTTF_SetFontStyle( font, TTF_STYLE_BOLD );
Any text rendered at this point will be in italics, but not boldTTF_SetFontStyle( font, TTF_STYLE_ITALIC );
Any text rendered at this point will be normal with no font stylesTTF_SetFontStyle( font, TTF_STYLE_NORMAL );
Any text rendered at this point will be underlinedTTF_SetFontStyle( font, STYLE_UNDERLINE );
As you can see, this is pretty straight forwards. So let's make things a little bit trickier by setting multiple font styles at once. But first we need to take a look at how to combine several flags like TTF_STYLE_ITALICS and TTF_STYLE_BOLD
Binary numbers
In order to learn about how to combine these flags, we need to look at binary numbers first of all. If you don't already know about binary numbers, you should take a look at the above link. It's not crucial, but it is highly recommended to know a little about them. I might create a blog post about them at some point.
A computer stores numbers as individual bits ( 0's and 1's ). They correspond to
on
/ off
or true
/ false
. Yup, that's right, we can use the individual bits of any number as a boolean variable. Let's take a look at an 8 bit binary number ( 1 byte )
1010 0101
As you can see, it has 8 digits. So that's eight different flags. Each of these flags have two different possible values :
0
/ 1
or false
/ true
. So that's 8 bool
s for the price of a single byte!Bitwise operations
So how do we use these 8 bools? As you know, we have the following boolean operations in C++ :
and
(&&
)or
(||
)
These work on an entire variable. And int, for instance will be false if its value is 0, otherwise its true. But there are similar operations that does this on all bits of a variable. These are called
bitwise
, simply because they operate on a simple byte. To do a bitwise operation, we need two variables of equal size ( same number of digits ), for instance two bytes. Let's create two bytes, we'll use these for a few examples
Byte 1 :Here is a simple example of bitwise0101 0011
( 64 + 16 + 2 + 1 = 85 )
Byte 2 :0110 0010
( 32 + 64 + 2 = 98 )
OR
between two bytes0101 0011And here's the
OR
0110 0010
=
0111 0011
AND
operation between the same two bytes :0101 0011
AND
0110 0010
=
0100 0010
We also have the a bitwise version of the
NOT
opeartion ( !
in C++ ). This operation only takes a single element and flips all bits ( turns 1's into 0's and 0's into 1's. ). Let's test it on our two bytesByte 1 :
NOT
0101 0011
= 1010 1100
Byte 2 :
Finally, there's a fourth operation called XOR or exclusive or. XOR is true when the first and second bool are different ( ie one is true, the other false ). It can be compared to theNOT
0110 0010
= 1001 1101
!=
operator which is true when the left and right sides are different. Here are the four possibilities :false XOR false = false
false XOR true = true
true XOR true = false
true XOR false = false
An here is the XOR between our two bytes:
0101 0011
XOR
0110 0010
=
0011 0001
As you can see, the positions where the bits are different is true, the others are false.
Setting and cheking individual bits
So now that we know how to do bitwise operations, we need a way of checking and setting the individual bits. This is done simply by using
OR
, AND
and XOR
. Before we take a look at how to do this, let's define a few values to check. Recall that the different font styles are ints? This is because they are used to perform bitwise operations to set and unset different bits. Here they are again, this time with their values. For simplicity, I'll only list the last four bits ( the others are always 0 ). The values are in decimal with the binary representation in parenthesis
- TTF_STYLE_NORMAL = 0 ( 0000 )
- TTF_STYLE_BOLD = 1 ( 0001 )
- TTF_STYLE_ITALIC = 2 ( 0010 )
- TTF_STYLE_UNDERLINE = 4 ( 0100 )
TTF_STYLE_STRIKETRHOUGH= 8 ( 1000 )
As you can see, they all have only one ( or zero ) bit set. This means we can use
AND
, OR
or XOR
on just one bit.Setting a bit
To set a bit ( without affect any other bit ) we use the
OR
operation. So say that we have four 0 bits, 0000
and we want to set the bit for bold on it. In other words, we want the result 0001
. What we do is : that we take our original 4 bits ( 0001 ) and set it to the original 4 bits ( 0001 ) OR
'ed with the bitmask for bold ( 0001 ) :0000
OR
0001 ( value of TTF_STYLE_BOLD )
=
0001
Simple as that! This woks for any of the other flags in the same way. They all will end up setting one bit.
Note that this will not change any other bits. If we try set the italics font style on the above variable we get
0001
OR
0010 ( value of TTF_STYLE_ITALIC )
=
0011 ( TTF_STYLE_BOLD and TTF_STYLE_ITALIC set )
Now that we know how to set the bits, we need a way of checking them
Unsetting a bit
Sometimes we want to remove a bit. Say for instance we want to remove the italics from the font above. How do we do that without affection the other values? This is a tiny bit more complex, because it requires two operations. What we are trying to do is the following. Say we have a bitmask ( 0000 1011 ) and we want to unset the bit for bold text, but leave the rest uncanged. So we need to be able to go from
0000 1011to
0000 1010Here we need to do an
AND
operation. This is because we have a bit that we want to set to 0. This would work fine if the bit already 0. But if it was 1, then any OR
operation with that bit would be true since an OR
operation only requires one of the elements to be 1 in order to return true. So we need to do an
AND
operation. But how? If we just used the TTF_STYLE_BOLD value ( 0000 0001 ) as the second element, we would end up with :0000 0101Which is clearly wrong. So what we need to do is to
AND
0000 0001
=
0000 0001
AND
it with a value that would not affect any bits except the one we want to unset. An AND
operation with 1 will do this as 0 AND
1 is 0 and 1 AND
1 is 0. And a AND
operation with 0 is guaranteed to unset the bit since 0 AND
0 is 0, and 1 AND
0 is 0. So what we need is a bit mask with one 0 ( the value we want to unset ) and the rest 1 ( the values we don't want to change. ) So this leaves us with the value 1111 1110. And AND
operation with this value will unset the last bit and leave the others as they were. And since 1111 1110 is the same as 0000 0001 ( TTF_STYLE_BOLD ) with all bits flipped, we can get this value by using the NOT
operation :NOT
0000 0001 ( TTF_STYLE_BOLD )
= 1111 1110
Now that we have our bitmask to
AND
with we get :0000 0101
AND
1111 1110
0000 0100
Checking a bit
To check a bit, we need to use the bitwise
AND
operation. And since we are only checking and not setting, we don't have to store the value anywhere which means we don't have to worry about changing anything. To check a bitmask, simply do an
AND
operation with the value you want to change for ( in this cae, any of the TTF_STYLE_....
values ). So, to check if a text is bold, we do an AND
between our mask an TTF_STYLE_BOLD
:0011 ( our bit mask,
TTF_STYLE_BOLD
and TTF_STYLE_ITALIC
set )AND
0001
=
0001
As you can see, we only set the bit that's set in our variable (
TTF_STYLE_ITALIC
set ) the others will be 0 no matter what our mask is. The value 0001 is not 0, and thus this evaluates to true and we now know that the font is bold. If our mask didn't have the bold bit set, our mask would be 0010. An AND
between 0010 AND
0001 is true ( they have no bit set to one in common ) and the result is 0 aka false.Putting it all to use
Now that we know about binary number and setting, getting and checking the bits, we can put it into use. Below is a simple class that serves as a wrapper for
TTF_Font
. It enables to set, get and check for flags using enum
. I hope this will make it easier to understand and use TTF_Font
s. For a full list of my tutorials / posts, click here. Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.
Etiketter:
C++,
C++11,
Cpp11,
enum classes,
font styles,
fonts,
game making tutorial,
Game programming,
make your own game,
rendering text,
SDL,
SDL tutorial,
SDL2 png,
SDL2 Tutorial,
SDL2_ttf,
ttf
mandag 14. juli 2014
[ SDL2 - Part 10 ] Text rendering.
Rendering text
In the previous parts, we've look at how to render rectangles and images, both with and without transparency. Now it's time to look at how we can render text.
Rendering text is tricky. You'll want to be able to render any font, in any size and preferably every possible character. Luckily, with the SDL ttf library, this is easy.
SDL2_ttf
SDL2_ttf
, just like SDL2_image
, is an additional library for SDL2
. It can use just about every font, and you can set the size too!What's TTF?
TTF
, or TrueType Fonts is a type of fonts developed by Apple and Microsoft in the late 90's. True Type Fonts offers a high degree of control on how the font looks. The internals of TTF fonts and how they work isn't important here. The important part is that they're easy to use, will look really nice ( even scaled up. ) And they're also widely used, so finding fonts shouldn't be a problem.SDL2 TTF?
As with SDL2_image, SDL2_ttf is a addon for SDL2 that deals with rendering text and makes it very easy. It is based on libfreetype, a library for writing text using TTF fonts. However, it's not very practical to use. SDL2_TTF makes using it a lot easier. But if you do want to use it yourself, you can take a look at their tutorial.
Setting up SDL2_TTF
Setting up SDL2 requires a tiny bit more work than
SDL2_image
, but don't be scared, it's still very easy. First we need to install the ttf
library. Installation
Installing
SDL2_ttf
is done exactly like SDL2_image. Just replace SDL2_image
with SDL2_ttf
Linux
For Linux you can use need to install
-lSDL2_ttf
or -libSDL2_ttf
or -SDL2_ttf
( the actual name might be different in different distributions. ) The linker flag is
-lSDL2_ttf
The process is more or less identical to that of setting up SDL2_image.
If you can't find
SDL2_ttf
in any repositories and it's not installed by default, you might have to compile it yourself. For more information, see my blog post about setting up SDL2.Windows
Similar to setting up SDL2 base.
The difference is that you have to download the development files for SDL2_ttf
And similarly add SDL2_ttf.lib to library includes and add SDL2_ttf.lib to the library flags ( where you previously added SDL2_image.lb )
And with that, it should work.
Mac
See the first part of my tutorial. Just install SDL2_ttf instead of SDL2
Initialization
Unlike
SDL2_image
does need to be initialized. Why? Because libfreetype
, the library that SDL2_ttf
builds upon needs to be initlaized, so naturally SDL_ttf
needs to be initalized too.Initializing SDL2_ttf requires a single function :
int TTF_Init()
Just like SDL_Init(Uint32 flags)
this function returns -1 on error. And just like with
SDL_Init(Uint32 flags)
, we should print an error if the function fails. SDL2_TTF
has its own function for printing errors :char *TTF_GetError()
This means our routine for initializing SDL2_ttf
will be the same as SDL2
, just with the two functions above ( see full code for details. )Loading fonts
This is the central structure of
SDL2_ttf
. It holds the font itself, the size and some other style information ( I'll go into this in the next part ). So, in order for us to use an TTF_Font
we need to load it. This is done using a load function :TTF_Font *TTF_OpenFont
(
const char *file,
int ptsize
)
So, the arguments are const char *file
- a pointer to the .ttf fileint ptsize
- the size of the font
NULL
pointer of it can't find the file, or there is another error ( like SDL2_ttf
isn't initialized. So this too should be handled by priting the error using TTF_GetError()
, just like when initializing ttf
Cleaning up fonts
Just like we with
SDL_Texture*
and SDL_Surface*
, we need to clean our fonts when done. This is just as easy for TTF_Font
s as with SDL_Texture*
and SDL_Surface*
. We simply call a function that does it for us : TTF_CloseFont
(
 TTF_Font* font
);
Rendering text
There are three functions you can use to render text, depending on what you want. Let's start with the first one :
TTF_RenderText_Solid
This function is used for quick and simple rendering of a text, using a specific font and a font color. The background of this is transparent. Here's the signature:
SDL_Surface *TTF_RenderText_Solid
(
TTF_Font *font,
const char *text,
SDL_Color fg
)
The arguments are :-
TTF_Font *font
- the font to use const char *text
- the text to renderSDL_Color fg
- the color to use for the text
The function returns the finished
SDL_Surface*
, or NULL if something went wrong ( like supplying a NULL
pointer for font
)
The result will look something like this :
TTF_RenderText_Blended
This function has the exact same signature as
TTF_RenderText_Solid
, so I'll just show it without explaining it parameter by parameter :So what's the difference betweenSDL_Surface *TTF_RenderText_Blended
(
TTF_Font *font,
const char *text,
SDL_Color fg
)
TTF_RenderText_Solid
and TTF_RenderText_Blended
? The difference is that TTF_RenderText_Solid
is very quick, but TTF_RenderText_Blended
produces a better result. In our game, we won't be updating our text surfaces all that often, and there's not a lot of them either, so TTF_RenderText_Blended
is a good choice. Here's what
TTF_RenderText_Blended
looks like :And here's a comparison between
TTF_RenderText_Solid
and TTF_RenderText_Blended
: The difference is not huge, but in the actual game it will be more clear. And the difference might also vary from font to font.
The third version is a tiny bit different :
TTF_RenderText_Shaded
This function will render the text, but with a specified background color.
SDL_Surface *TTF_RenderText_Solid
(
TTF_Font *font,
const char *text,
SDL_Color fg,
SDL_Color bg
)
The arguments are :-
TTF_Font *font
- the font to use const char *text
- the text to renderSDL_Color fg
- the color to use for the textSDL_Color bg
- the color to use for the background
So it's almost the same as the other two, just with a third argument for the background color. The return value is also the same as the other two.
The result will look something like this :
An example
Below is a simple example that should run and compile out of the box. For compilation details, look below.
Running it!
Running it is just as simple as with
SDL2_image
. So that means compilation on Windows is already set up when you installed TTF
Linux / Mac
If you are compiling using the compiler, you have to add
-lSDL2_ttf
to the compile string like so :clang++ main.cpp -std=c++11 -o Game -lSDL2 -lSDL2_image -lSDL2_ttf
If you want to run it, you simply do
./Game
Updated game code
I have done a bit of cleaning up in the game code. I've added a new Texture class for text, cleaned up include, removed ( and added ) comments, improve delta calculation++ Everything should be explained in comments, but, of course, if you have any questions of any kinds, just comment or contact me, I'll be happy to help.
You can find the code here.
For a full list of my tutorials / posts, click here.
Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.
tirsdag 8. juli 2014
Contents of this blog
Contents
Below you'll find a list of all the tutorials with a list of part and a short description of each part.
Game programming in SDL2
A tutorial about making games. Focuses a lot on SDL 2, but also other topics related to game programming. Some parts are pretty extensive so that you'll get a deeper understanding about what's going on.
Part 1 - Setting up SDL 2
This part gives you a quick introduction to SDL 2. It'll help you set up SDL2 including header files, linking and everything you need to get SDL 2 up and running. It also contains a bare-boned SDL2 program.
Part 2 - Your first SDL 2 application
Here we take a look at the two basic structs of SDL 2,
SDL_Window
and SDL_Renderer
. It'll help you understand what these do and how to initialize them and the basics of SDL2 correctly. We also take a look at how to set the render color ( which also works as background color )Part 3 - Drawing rectangles
Here we take a look at
SDL_Rect
and how to render it with SDL_RenderDrawRect
and SDL_RenderFillRect
.Part 4 - Making things happen
About events in SDL 2. How to get the events, how the event structure,
SDL_Event
laid out, and how we do we handle the event.Part 5 - Collision detection and our first game!
We learn how to check for collisions and make a simple game out of it.
Part 6 - Let's load some textures!
In this part we take a look at
SDL_Texture
s and how to create them from image files ( bmp. ) We also look at how to render SDL_Texture
s.Part 7 - Using PNG files
We look at, and set up,
SDL_Image
library. This is used to load png files which then can be rendered with the transparency layer. Part 8 - It's TIME for another update!
Here we look at a new rendering function,
SDL_RenderCopyEx
. The function lets us render textures rotated. We use it to make a simple analog clock.Part 9 - No more delays!
Up until now we've used
SDL_Delay
to limit framerate to ~16 FPS. We look at how to render using a delta time. Part 10 - Text Rendering
Text rendering in games can be tricky. Luckily,
SDL2_TTF
does this for us. This part talks about SDL2_TTF
, how to set it up and how to use it. C++11 Features
A series about the new features of C++11. I will try to explain everything thoroughly and extensively. The goal is to learn about C++11, how the various parts of it works and why you should use them.
I will cover both minor features like
enum classes
and larger ones like chrono
Part 1 - Enum classes
This is a very short part about the new version of
enums
, enum class
es. It covers what they are and why you should use them as much as possiblePart 2 - Timing with chrono
This part is very extensive and covers the new timing features of
C++11
, My SDL2 Tutorial part 9 ( No more delays! ) also deals with
chron
but this part covers it more extensively.Part 3 - Smart pointers in C++11
About smart pointers in C++11 (
unique_ptr
, shared_ptr
and weak_ptr
) what they are, how they work and how to use them. You should always use smart pointers unless you have a really good to.
mandag 7. juli 2014
[ C++11 - Part 2 ] Timing with chrono
std::chrono
The
chrono
library is a new part of C++11. It's used for timing functionality. It supports a variety of different operations and can be quite daunting in the beginning. I wrote a little about it in the previous part of my SDL2 tutorial, but I thought I'd add some more details and try to explain things a bit more throughly here. The library contains several new types, but I'll try to explain all of them shortly. But first a few practical tips that will make using std::chrono
easier. Some helpful tips
My first recommendation is that you write
using namespace std::chrono
somewhere your code ( preferably at the top ). Usually, using namespace
statements are discouraged to avoid name collisions, but this is less likely to occur when you write using namespace std::[something]
where something can be anything like string
or cout
or indeed chrono
. The reason why we use this is quite simple. If we didn't use it, we'd end up with code with std::chrono
all over the place. It's a lot easier ( at least for me ) to read time_point p = high_resolution_clock::now();
Than it is to read
std::chrono::time_point p = std::chrono::high_resolution_clock::now();
Includes
To make sure everything compiles as it should, please add the headers and
using
to the top of your file :
#include
#include
using namespace std::chrono: // So we don't have to write std::chrono everywhere!
About this part
I also want to point out that this guide is rather comprehensive. This is because it's a good idea to have deep understanding how the various components of the
chrono
library works. Just writing this guide helped me to understand a lot of it. My hope is that it will help you to. If you want more information, you can look at the en.cppreference documentation for chrono.
en.cppreference covers most, if not all of the features in C++11 with details information and good examples. It's very useful for learning and as a reference.
Overview
Here is a quick overview of the chrono library.
I will now go over each of these components, starting with the basic one,
ratio
ratio
The ratio is actually not a direct part of
chrono
but it's very useful for working with chrono
and just useful in genera. ration
is define in it's own header file, simply called ratio
The class is self is template. The definition looks like the following :
template
<
std::intmax_t Num,
std::intmax_t Denom = 1
>
class ratio;
Even though this is a templated class, which can be complicated and hard to understand, this one is quite simple. The first part ( Num
)is the numerator, or simply the top part of a fraction. And naturally, the second one, ( Denom
) is the denomerator. These two numbers forms a fractions : Num
/ Denom
, usually these numbers are dividable by 10, so you end up with numbers like 1000
or 0.001
Both variables are of the type
std::intmax_t
, which means they are the biggest possible type of signed
int
available, usually at least 64 bits. In the chrono library ticks are stored in fractions of second. A few examples :
- A second :
ratio < 1 /1 >
- A second is 1 /1 of a second.
- Can also be written as
std::ratio< 1 >
since the denomerator defaults to 1 - A millisecond :
ratio< 1 /1000 >
- A millisecond is 1 / 1000 of a second
- A minute :
ratio< 60 /1 >
- A minute is 60 / 1 seconds.
- Can also be written as
ratio< 60 >
since the denomerator defaults to 1
As a part of the ratio header, there's a lot of predefined ratios ( milli, nano, kilo, mega, ... ). Because ratio is not directly a part of the
chrono
library ( it has other uses than just chrono ), there is no predefined values for minutes, hours, etc. There's just the ones based on the standard units that's used for distance or weight. You can refer to either of these using the std::
prefix. Since the ratio
s are not part of the chrono
library, it's just std::
and not std::chrono::
.For a full list of the predefined values, take a look at the documentation.
duration
duration( or
std::chrono::duration
) is one of the basic classes of the chrono library. As the name suggests, it stores a time duration or a distance between to points in time if you like.Member variables
It's quite simple in its structure, it only has two member variables :
rep
- an arithmetic type ( basically any unit that can hold a number ) that stores the number of ticks.
period
- a ration containing the type of unit
rep
stores. In effects this means "how many seconds is one tick."
Constructing a duration
The constructor for a duration is rather complex as it involves both rep ( the type of the variable to hold the tick ) And setting the period i.e. how long each tick is ( millisecond, second, minute, ... )
The actual constructor looks like this :
template
<
class Rep,
class Period = std::ratio<1>
>
class duration;1>
The ratio defaults to 1 / 1, meaning seconds. The number of ticks the duration should use are sent to the constructor in the ordinary way using the parenthesis after the variable nameSo to create a
duration
for containing seconds, you can do :duration< int64_t > durSec( 10 );
This duration is for 10 seconds. Nice and simple!To create different durations we can use the
ration
like above. For example 10 millisecond becomes : duration< int64_t, std::ratio< 1, 1000 > > durMSec( 10 );
Okay, but how about 10 minutes? This is quite similar to the above :duration< int64_t, std::ratio< 60, 1 > > durMin( 10 );
We do
std::ratio< 60, 1 >
because a minute is 60 / 1 seconds which is the same as 60 seconds. As I mentioned above, there are predefined ratios already in C++11. So you can simplify a duration of 10 milliseconds to :
duration< int32_t, std::milli > dur( 10 );
But there is an even simpler way! Just like in ratio
there are predefined values in duration
And they're very simple to use. So say you wanted a duration of 10 milliseconds :
milliseconds mSec( 10 );
Member functions
Duration has a few functions, but I'll just cover two of them now
- count
- Returns the number of ticks (rep , see the documentation above )
- duration_cast
- Converts a duration of duration into another.
duration_cast
When we're working with two different types of
duration
s things can get a bit weird. A simple example : what's 3 minutes minus 36 seconds. The answer is quite simple for a human to figure out. But the results in your program needs to be a duration
but which unit? Since 3:24 can't be expressed exactly in minutes, we need to change either the 3 minutes to seconds, or the 36 seconds to minutes. Here's where duration_cast<>
comes in.
minutes min( 3 );
seconds sec( 36 );
minutes minToSec = duration_cast< std::chrono::seconds > ( min );
This simply casts our 3 minutes into 180 seconds. After doing that, you can simply do
seconds result = minToSec - sec;
And you'll get the result in seconds ( 144. ) If you had converted this to minutes, it would take the whole minutes and strip the seconds. Meaning you would end up with 2Floating points
Up until now, we've only used integer values for representing ticks. This means we can only represent whole tick. So a
duration
of seconds using an int
to represent ticks means it can only represent whole seconds. Consider the following code : Create a duration of milliseconds represented as int :
duration< int32_t, std::milli > msAsInt( 16 );c
This is equal to the std::milliseconds
Create a duration of seconds represented as int :
duration< int32_t, std::ratio< 1, 1> > secAsInt( msAsInt );c
This is equal to std::seconds
. This will fail ( won't compile ) because you loose precision. You could use a duration_cast
to fix this, but you'll loose the precision and end up with 0. duration_cast
is way of telling the compiler "yeah, I know this will fail loose precision, just do it!"Create a duration of seconds represented as a double :
duration< double, std::ratio< 1, 1> > secAsDouble(msAsInt);
This is not the same as std::seconds because std::second uses int. If you did secondsAsFloat.count() you would get 0.016, because ticks are stored in a double.Unlike the previous line, this won't fail because you don't loose precision. In fact, you could change the
std::ratio
to store years :duration< double, std::ratio< 60 * 60 * 24 * 365, 1 > > yearsAsDouble( millisecondsAsInt ); // 0.016 sec
This will convert the 16 ms to years. If you did yearsAsDouble.count()
you would get roughly 5.07356672 × 10^-9 ( 0.00000000507356672 ) years.Clocks
In order to set the time for a
time_point
you use clocks. Clocks are just object that has a starting point ( or epoch ) and a tick rate. The starting point is often 1.1.1970 ( UNIX timestamp ) and the tick rate can be 1 second for instance. There are three available clocks in the
chrono
library. - system_clock
- The wall clock, use this if you just want to know the current time and date
- This clock may be adjusted by either daylight saving time ( DST ) or leap seconds.
- Can be mapped to C-style points and can therefore be easily printed.
- steady_clock
- This clock is monotonic, this means will never be adjusted, so it'll never be affected by things like DST and leap seconds
- Best suited for measuring intervals
- high_resolution_clock
- Shortest tick available ( will be updated most often )
- Might be alias of std::chrono::system_clock or std::chrono::steady_clock, or a third, independent clock.
- This means it's not guaranteed to be monotonic like steady_clock
- Use this for benchmarking
time_point
time_point ( or
std::chrono::time_point
) is the central class of the chrono library. I'll spare the implementation details for now and just say that it contains various information about the current point it in time.Construction
The constructor for a time point looks something like this :
template
<
class Clock,
class Duration = typename Clock::duration
>
class time_point;
As you can see, its a templated class that takes both a clock, and a duration. The duration is the duration the time_point
uses. This defaults to the duration of the clock ( as you can see from the n = typename Clock::duration
part. ) Luckily though don't have to specify either of the arguments. SO a really simple way to construct a time_point
is :high_resolution_clock::time_point timePoint1;
You can use stady_clock
or system_clock
instead of high_resolution_clock
.This is equal to writing :
time_point < high_resolution_clock > timePoint1;
A bit more complex, but it means the exact same time : a time_point
that uses a high_resolution_clock
and has the same duration as the high_resolution_clock
. I see no reason to use this method as opposed to the simpler one, but I want to explain the template arguments as well as I can. But what if we wanted to specify our own duration? Well then we have to set the second template argument as well. Say we wanted to specify milliseconds as our duration time unit. Remember that duration has predefined constants for this. This means we can simply do :
time_point < high_resolution_clock, milliseconds >
Now let's do something even worse. Say we wanted a time_point
that uses half minutes ( 30 seconds ) as the time units. I have no idea why anyone would want this, but it's just a ( somewhat contrived ) example. As with some other examples, I do not encourage writing code like this. It's horribly hard to read and very hard to understand unless you know the different part of the chrono
librarer. Okay, here we go :time_point
<
high_resolution_clock,
std::chrono::duration
<
int64_t,
std::ratio
<
30,
1000
>
>
>
Yup, that's a template argument, inside a template argument, inside a template argument! Don't write code like this! If you need a specific time ration, at least put the ration
and duration
in separate objects ( I've shown you how to use the constructors above. ) Also make sure they're well named like ratio30sec
and duration30sec
. That should give the reader at least a small hope at knowing what's going on. Functions
With that out of the way, let's move on to some functions, shall we? Well actually there's just one member function in the three different clocks :
now()
The function returns the current time as a time_point
. As a time_point
, you say? Well that means we can just use auto
to create one!auto time = high_resolution_clock::now()
Simple as that, we've created a time point. The type of this object will be exactly the same as
high_resolution_clock::time_point timePoint1;
Only it's even simpler. And we've initialized it to the current point in time at the same time!
Difference between two time_points
Time points supports arithmetic operations. This means you can say time_point x - time_point y. This will result in a
duration
But what is the kind of duration. Seconds? Milliseconds? Years? Actually there is no one answer, it's implementation defined. But the result is almost certain to be less than one second. Since we can't know what the resulting value is, we have two options. Say we have the following code :
clock::time_point t1 = std::chrono::system_clock::now() ;
// ...
clock::time_point t2 = std::chrono::system_clock::now() ;
And wanted to know the result of
t1 - t2
Our two options are : - Use
atuto
- auto
result = t2 - t1 ;
- Nice and simple
- But we can't control the type of result.
- Use
duration_cast
-
milliseconds result = duration_cast< milliseconds > ( t2 - t1 ) ;
- Takes the result of
t2 - t1
and casts it into a duration of milliseconds and uses it to set result - This is a bit harder, but now we have full control over the type of duration.
For a full list of my tutorials / posts, click here.
Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.
Abonner på:
Innlegg (Atom)