In this tutorial, we will transform a short program from legacy C++ into C++11, and learn a few of the new features along the way.
The tutorial is designed for you to follow along hands-on: We will alternate between meeting a feature and integrating it into our code. If you want to get your hands dirty, the presentation slides and code samples are available on
git clone https://github.com/nikolausmayer/cpp11presentation
(please use GCC/g++ ≥ 4.7) but you are not required to do so.
I am not an expert in C++(11). I may or may not be able to answer technical questions.
The code examples are designed to highlight specific language features, and may not always represent best code practices or sensible program design.
If you are using Boost, you may already know much of what follows.
auto
for
loops -std=c++11
(-std=c++0x
for GCC ≤ 4.6) sudo apt-get install g++-4.9 gcc-4.9
git clone https://github.com/nikolausmayer/cpp11presentation
code/Makefile
-std=c++11
to CXXFLAGS
make
to make sure it works. If you had to install a new g++, you might want to use CXX=g++-4.9 make
auto
auto
keyword is not new, but its meaning has changed static
, extern
)
float almostpi = 3.14f;
std::vector<SomeClass<SomeType> >::iterator it = myvec.begin();
auto almostpi = 3.14f;
auto it = myvec.begin();
auto
ignores references and const
/volatile
(there are special cases) auto
, decltype
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) {
return t1 + t2;
}
auto v = add(2, 3.14);
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2) {
return t1 + t2;
}
auto v = add(2, 3.14);
decltype
can do anything that auto
can. Why not write...
template <typename T1, typename T2>
decltype(t1 + t2) add(T1 t1, T2 t2) { // (a)
return t1 + t2;
}
decltype(add(2, 3.14)) v = add(2, 3.14); // (b)
t1
and t2
are technically unknown at that point -std=c++1y
in GCC 4.8, -std=c++14
in GCC≥4.9):
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) {
return t1 + t2;
}
int i = 5;
ProbabilityModel obj = m[i];
std::pair<int,int> p = m_hocos[0];
std::cout << obj.prob(p);
|
|
auto
is a double-edged sword. If you let it loose, it will destroy your code. Use it wisely, and keep in mind: Either declaration or handle must be informative.
auto point = m_homogeneous_coords[0];
std::cout << model.prob(point);
std::vector
for (unsigned int i = 0; i < m_homogeneous_coords.size(); ++i)
std::cout << model.prob(m_homogeneous_coords[i]);
i
. The “good” solution (which also works for e.g. std::map
) is far worse still:
for (std::vector<std::pair<int,int> >::iterator it = m_...begin();
it != m_homogeneous_coords.end();
++it)
std::cout << model.prob(*it);
for point in m_homogeneous_coords:
print(point)
for (auto point: m_homogeneous_coords)
std::cout << model.prob(point);
for (const auto& point: m_homogeneous_coords)
std::cout << model.prob(point);
auto
way does not work.
std::vector<myClass*> vec;
myClass* instance = new myClass(...);
vec.push_back(instance);
<memory>
header and write
std::vector<std::unique_ptr<myClass> > vec;
vec.push_back(std::unique_ptr<myClass>(new myClass(...));
vec
is destroyed, all of its elements are automatically and safely destroyed as well.
unique_ptr
is useful if an instance is never shared and never copied. To get the full functionality of naked pointers (e.g. copyability), use shared_ptr
instead.
std::vector<std::shared_ptr<myClass> > vec;
std::shared_ptr<myClass> myClass_ptr{0};
myClass_ptr = std::make_shared<myClass>(...);
vec.push_back(myClass_ptr);
*
and ->
, as well as checked for not-null using
if (myClass_ptr) {...}
unique_ptr
has virtually no overhead; a shared_ptr
has some due to e.g. reference counting (weak_ptr
can help).
std::shared_ptr<myClass> myClass_ptr{0};
then you are right. The correct way to write this used to be
std::shared_ptr<myClass> myClass_ptr = 0;
or
std::shared_ptr<myClass> myClass_ptr = {0};
class myClass;
void Func() {
int foo(myClass());
}
foo
?
int
variable myClass
and returns an int
myClass
, and returns an int
class myClass;
void Func() {
int foo{myClass{}};
}
foo
can only be a variable of type int
. This is almost universally preferrable to legacy style. The only real restriction is that narrowing is no longer allowed:
int a(5.2); // ok, a==5
int a{5.2}; // error!
int main() {
std::vector<float> numbers;
...
auto multiplier = [&numbers](float factor) -> void {
for (auto& elem: numbers) elem *= factor;
};
multiplier(3.0f);
}
multiplier
is a function. It
numbers
by reference, float
argument factor
, void
(remember trailing return?), numbers
by factor
. auto
is necessary because the type of a lambda function is complicated and inferred at compile time.
[](){}();
is an in-place definition and immediate call of an anonymous void
function which takes no parameters and does nothing. It is also completely legal.
def argmin(sequence):
min_index = ...
min_value = ...
return [min_index, min_value]
mini, minv = argmin(v)
|
|
template <typename T>
std::tuple<int, T> argmin(std::vector<T>& v)
{
int min_index = {...}
T min_value = {...}
return std::make_tuple(min_index, min_value);
}
...
int mini;
float minv;
std::tie(mini, minv) = argmin(numbers);
std::tuple
can take arbitrarily many elements and types.
std::tie(mini, std::ignore) = argmin(numbers);
#if __cplusplus > 199711L
std::cout << "Compiled using C++11 or later\n";
#else
std::cout << "Compiled using C++03 or earlier\n";
#endif
__cplusplus
are 199711L
(C++98/C++03 aka "C++"), 201103L
(C++11) and 201402L
(C++14).
#ifdef HAS_MOVE_SEMANTICS
std::vector<std::vector<int>> vec;
What is wrong with this line?
GCC says:
error: '>>' should be '> >' within a nested template argument list
std::vector<std::vector<int> > vec;
int stupid_division(int a, int b) {
assert(b != 0);
return (a / b);
}
template <int N>
class Filter {
...
}
What if Filter
is only valid for N>1
?
template <int N>
class Filter {
Filter() {
if (N<=1) throw std::runtime_error("Filter invalid for N<=1");
}
...
}
template <int N>
class Filter {
Filter() {
static_assert(N>1, "Filter invalid for N<1");
}
...
}
template <typename T>
T very_small_value() {
return (T)0.00001;
}
What if T
is an integer type?
template <typename T>
T very_small_value() {
throw std::runtime_error("Bad type");
return (T)0.00001;
}
template float very_small_value<float>() { return 0.00001f; }
template double very_small_value<double>() { return 0.00001; }
...
template <typename T>
T very_small_value() {
return (T)0.00001;
}
What if T
is an integer type?
template <typename T>
T very_small_value() {
static_assert(std::is_floating_point<T>::value,
"very_small_value only works for float types");
return (T)0.00001;
}
type_traits
header:is_enum
, is_const
, is_move_assignable
...
enum
... enum
s have underlying types
Standard C++ 7.2/5:
The underlying type of an enumeration is an integral type
that can represent all the enumerator values defined in the
enumeration.
they are effectively just integers.
enum boyfriend_type { rich, nice, cool };
enum CERN_magnet_status { warm, cool, supercool };
error: redeclaration of ‘cool’
note: previous declaration ‘boyfriend_type cool’
enum
... (cont.)
enum class boyfriend_type { rich, nice, cool };
enum struct CERN_magnet_status { warm, cool, supercool };
...
int magnet = CERN_magnet_status::supercool; // not ok
CERN_magnet_status magnet = CERN_magnet_status::supercool; // ok
enum class boyfriend_type : short { rich, nice, cool };
enum CERN_magnet_status : int { warm, cool, supercool };
type_values = {int: 1, float: 2}
std::map<std::typeinfo, int> type_values;
type_values[typeid(int)] = 1;
template <typename T>
struct type_values { static const int value = 0; }
template struct type_values<int> { static const int value = 1; }
std::map<std::typeinfo, int> type_values;
type_values[typeid(int)] = 1;
type_index
:
std::map<std::type_index, int> type_values;
type_values[std::type_index(typeid(int))] = 1;
auto
decltype
,
initializer lists,
constexpr
,
Constructors calling other constructors of the same class,
override
and final
keywords,
rvalue references,
[[noreturn]]
and other attributes,
Variadic templates,
std::nullptr_t
,
type traits,
std::thread
,
std::promise
,
std::future
,
std::packaged_task
,
static_assert
,
std::chrono::steady_clock
,
class enum
,
std::I_hope_this_is_too_small_to_read
, ...
auto