Você está na página 1de 9

5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject

12,958,457 members 71,751 online Sign in

Searchforarticles,questions,tips
articles Q&A forums lounge

Ten C++11 Features Every C++ Developer Should Use


Marius Bancila, 2 Apr 2013 CPOL Rate this:
4.95 151 votes

This article discusses a series of features new to C++11 that all developers should learn and use.

This article discusses a series of features new to C++11 that all developers should learn and use. There are lots of new additions to the language and the standard
library, and this article barely scratches the surface. However, I believe some of these new features should become routine for all C++ developers. You could probably
find many similar articles evangelizing different C++11 features. This is my attempt to assemble a list of C++ features that should be a norm nowadays.

Table of contents:

auto
nullptr
Rangebased for loops
Override and final
Stronglytyped enums
Smart pointers
Lambdas
nonmember begin and end
static_assert and type traits
Move semantics

auto
Before C++11 the auto keyword was used for storage duration specification. In the new standard its purpose was changed towards type inference. auto is now a sort
of placeholder for a type, telling the compiler it has to deduce the actual type of a variable that is being declared from its initializer. It can be used when declaring
variables in different scopes such as namespaces, blocks or initialization statement of for loops.

Hide Copy Code


autoi=42;//iisanint
autol=42LL;//lisanlonglong
autop=newfoo();//pisafoo*

Using auto usually means less code unless your type is int which is one letter shorter. Think of iterators in STL that you always had to write while iterating over
containers. It makes obsolete creating typedefs just for the sake of simplicity.

Hide Copy Code


std::map<std::string,std::vector<int>>map;
for(autoit=begin(map);it!=end(map);++it)
{
}

You should note that auto cannot be used as the return type of a function. However, you can use auto in place of the return type of function, but in this case the
function must have a trailing return type. In this case auto does not tell the compiler it has to infer the type, it only instructs it to look for the return type at the end of
the function. In the example below the return type of function compose is the return type of operator+ that sums values of types T1 and T2.

Hide Copy Code


template<typenameT1,typenameT2>
autocompose(T1t1,T2t2)>decltype(t1+t2)
{
returnt1+t2;
}
autov=compose(2,3.14);//v'stypeisdouble

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 1/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject

nullptr
Zero used to be the value of null pointers, and that has drawbacks due to the implicit conversion to integral types. The keyword nullptr denotes a value of type
std::nullptr_t that represents the null pointer literal. Implicit conversions exists from nullptr to null pointer value of any pointer type and any pointertomember
types, but also to bool as false. But no implicit conversion to integral types exist.

Hide Copy Code


voidfoo(int*p){}

voidbar(std::shared_ptr<int>p){}

int*p1=NULL;
int*p2=nullptr;
if(p1==p2)
{
}

foo(nullptr);
bar(nullptr);

boolf=nullptr;
inti=nullptr;//error:Anativenullptrcanonlybeconvertedtoboolor,usingreinterpret_cast,toanintegraltype

For backward compatibility 0 is still a valid null pointer value.

Rangebased for loops


C++11 augmented the for statement to support the "foreach" paradigm of iterating over collections. In the new form, it is possible to iterate over Clike arrays,
initializer lists and anything for which the nonmember begin() and end() functions are overloaded.

This for each for is useful when you just want to get and do something with the elements of a collection/array and don't care about indexes, iterators or number of
elements.

Hide Copy Code


std::map<std::string,std::vector<int>>map;
std::vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
map["one"]=v;

for(constauto&kvp:map)
{
std::cout<<kvp.first<<std::endl;

for(autov:kvp.second)
{
std::cout<<v<<std::endl;
}
}

intarr[]={1,2,3,4,5};
for(int&e:arr)
{
e=e*e;
}

Override and final


I always founded the virtual methods badly designed in C++ because there wasn't and still isn't a mandatory mechanism to mark virtual methods as overridden in
derived classes. The virtual keyword is optional and that makes reading code a bit harder, because you may have to look through the top of the hierarchy to check if the
method is virtual. I have always used, and encouraged people to use the virtual keyword on derived classes also, to make the code easier to read. However, there are
subtle errors that can still arise. Take for instance the following example:

Hide Copy Code


classB
{
public:
virtualvoidf(short){std::cout<<"B::f"<<std::endl;}
};

classD:publicB
{
public:

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 2/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject
virtualvoidf(int){std::cout<<"D::f"<<std::endl;}
};

D::f is supposed to override B::f. However, the signature differ, one takes a short, one takes an int, therefor B::f is just another method with the same name
and overload and not an override. You may call f() through a pointer to B and expect to print D::f, but it's printing B::f.

Here is another subtle error: the parameters are the same, but the method in the base class is marked const, while me method in the derived is not.

Hide Copy Code


classB
{
public:
virtualvoidf(int)const{std::cout<<"B::f"<<std::endl;}
};

classD:publicB
{
public:
virtualvoidf(int){std::cout<<"D::f"<<std::endl;}
};

Again, these two are overloads and not overrides, so if you call f() through a pointer to B it will print B::f and not D::f.

Fortunately there is now a way to describe your intentions. Two new special identifiers not keywords have been added: override, to indicate that a method is
supposed to be an override of a virtual method in a base class, and final, to indicate that a derived class shall not override a virtual method. The first example would
become:

Hide Copy Code


classB
{
public:
virtualvoidf(short){std::cout<<"B::f"<<std::endl;}
};

classD:publicB
{
public:
virtualvoidf(int)override{std::cout<<"D::f"<<std::endl;}
};

This now triggers a compiler error the same error you'd get for the second example too, if using the override specifier:

'D::f' : method with override specifier 'override' did not override any base class methods

On the other hand if you intend to make a method impossible to override any more down the hierarchy mark it as final. That can be in the base class, or any
derived class. If it's in a derived classes you can use both the override and final specifiers.

Hide Copy Code


classB
{
public:
virtualvoidf(int){std::cout<<"B::f"<<std::endl;}
};

classD:publicB
{
public:
virtualvoidf(int)overridefinal{std::cout<<"D::f"<<std::endl;}
};

classF:publicD
{
public:
virtualvoidf(int)override{std::cout<<"F::f"<<std::endl;}
};

function declared as 'final' cannot be overridden by 'F::f'

Stronglytyped enums
"Traditional" enums in C++ have some drawbacks: they export their enumerators in the surrounding scope which can lead to name collisions, if two different enums in
the same have scope define enumerators with the same name, they are implicitly converted to integral types and cannot have a userspecified underlying type.

These issues have been fixed in C++ 11 with the introduction of a new category of enums, called stronglytyped enums. They are specified with the enumclass
keywords. They no longer export their enumerators in the surrounding scope, are no longer implicitly converted to integral types and can have a userspecified
underlying type a feature also added for traditional enums.

Hide Copy Code

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 3/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject

enumclassOptions{None,One,All};
Optionso=Options::All;

Smart pointers
There have been tons of articles written on this subject, therefore I just want to mention the smart pointers with reference counting and auto releasing of owned
memory that are available:

unique_ptr: should be used when ownership of a memory resource does not have to be shared it doesn't have a copy constructor, but it can be transferred to
another unique_ptr move constructor exists.
shared_ptr: should be used when ownership of a memory resource should be shared hence the name.
weak_ptr: holds a reference to an object managed by a shared_ptr, but does not contribute to the reference count; it is used to break dependency cycles think
of a tree where the parent holds an owning reference shared_ptr to its children, but the children also must hold a reference to the parent; if this second
reference was also an owning one, a cycle would be created and no object would ever be released.

On the other hand the auto_ptr is obsolete and should no longer be used.

When you should unique_ptr and when you should use shared_ptr depends on the ownership requirements and I recommend reading this discussion.

The first example below shows unique_ptr. If you want to transfer ownership of an object to another unique_ptr use std::move I'll discuss this function in
the last paragraph. After the ownership transfer, the smart pointer that ceded the ownership becomes null and get() returns nullptr.

Hide Copy Code


voidfoo(int*p)
{
std::cout<<*p<<std::endl;
}
std::unique_ptr<int>p1(newint(42));
std::unique_ptr<int>p2=std::move(p1);//transferownership

if(p1)
foo(p1.get());

(*p2)++;

if(p2)
foo(p2.get());

The second example shows shared_ptr. Usage is similar, though the semantics are different since ownership is shared.

Hide Copy Code


voidfoo(int*p)
{
}
voidbar(std::shared_ptr<int>p)
{
++(*p);
}
std::shared_ptr<int>p1(newint(42));
std::shared_ptr<int>p2=p1;

bar(p1);
foo(p2.get());

The first declaration is equivalent to this one

Hide Copy Code


autop3=std::make_shared<int>(42);

make_shared<T> is a nonmember function and has the advantage of allocating memory for the shared object and the smart pointer with a single allocation, as
opposed to the explicit construction of a shared_ptr via the contructor, that requires at least two allocations. In addition to possible overhead, there can be situations
where memory leaks can occur because of that. In the next example memory leaks could occur if seed throws an error.
Hide Copy Code
voidfoo(std::shared_ptr<int>p,intinit)
{
*p=init;
}
foo(std::shared_ptr<int>(newint(42)),seed());

No such problem exists if using make_shared. The third sample shows usage of weak_ptr. Notice that you always must get a shared_ptr to the referred object by
calling lock, in order to access the object.
Hide Copy Code
autop=std::make_shared<int>(42);
std::weak_ptr<int>wp=p;

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 4/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject
{
autosp=wp.lock();
std::cout<<*sp<<std::endl;
}

p.reset();

if(wp.expired())
std::cout<<"expired"<<std::endl;

If you try to lock on an expired weak_ptr the object is weakly reference has been released you get an empty shared_ptr.

Lambdas
Anonymous functions, called lambda, have been added to C++ and quickly rose to prominence. It is a powerful feature borrowed from functional programming, that in
turned enabled other features or powered libraries. You can use lambdas wherever a function object or a functor or a std::function is expected. You can read
about the syntax here.

Hide Copy Code


std::vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

std::for_each(std::begin(v),std::end(v),[](intn){std::cout<<n<<std::endl;});

autois_odd=[](intn){returnn%2==1;};
autopos=std::find_if(std::begin(v),std::end(v),is_odd);
if(pos!=std::end(v))
std::cout<<*pos<<std::endl;

A bit trickier are recursive lambdas. Imagine a lambda that represents a Fibonacci function. If you attempt to write it using auto you get compilation error:

Hide Copy Code


autofib=[&fib](intn){returnn<2?1:fib(n1)+fib(n2);};

Hide Copy Code


errorC3533:'auto&':aparametercannothaveatypethatcontains'auto'
errorC3531:'fib':asymbolwhosetypecontains'auto'musthaveaninitializer
errorC3536:'fib':cannotbeusedbeforeitisinitialized
errorC2064:termdoesnotevaluatetoafunctiontaking1arguments

The problem is auto means the type of the object is inferred from its initializer, yet the initializer contains a reference to it, therefore needs to know its type. This is a
cyclic problem. The key is to break this dependency cycle and explicitly specify the function's type using std::function.

Hide Copy Code


std::function<int(int)>lfib=[&lfib](intn){returnn<2?1:lfib(n1)+lfib(n2);};

nonmember begin and end


You probably noticed I have used in the samples above nonmember begin() and end() functions. These are a new addition to the standard library, promoting
uniformity, consistency and enabling more generic programming. They work with all STL containers, but more than that they are overloadable, so they can be extended
to work with any type. Overloads for Clike arrays are also provided.

Let's take for instance the previous example where I was printing a vector and then looking for its first odd element. If the std::vector was instead a Clike array, the code
might have looked like this:

Hide Copy Code


intarr[]={1,2,3};
std::for_each(&arr[0],&arr[0]+sizeof(arr)/sizeof(arr[0]),[](intn){std::cout<<n<<std::endl;});

autois_odd=[](intn){returnn%2==1;};
autobegin=&arr[0];
autoend=&arr[0]+sizeof(arr)/sizeof(arr[0]);
autopos=std::find_if(begin,end,is_odd);
if(pos!=end)
std::cout<<*pos<<std::endl;

With nonmember begin() and end() it could be put as this:

Hide Copy Code


intarr[]={1,2,3};
std::for_each(std::begin(arr),std::end(arr),[](intn){std::cout<<n<<std::endl;});

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 5/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject

autois_odd=[](intn){returnn%2==1;};
autopos=std::find_if(std::begin(arr),std::end(arr),is_odd);
if(pos!=std::end(arr))
std::cout<<*pos<<std::endl;

This is basically identical code to the std::vector version. That means we can write a single generic method for all types supported by begin() and end().
Hide Shrink Copy Code
template<typenameIterator>
voidbar(Iteratorbegin,Iteratorend)
{
std::for_each(begin,end,[](intn){std::cout<<n<<std::endl;});

autois_odd=[](intn){returnn%2==1;};
autopos=std::find_if(begin,end,is_odd);
if(pos!=end)
std::cout<<*pos<<std::endl;
}

template<typenameC>
voidfoo(Cc)
{
bar(std::begin(c),std::end(c));
}

template<typenameT,size_tN>
voidfoo(T(&arr)[N])
{
bar(std::begin(arr),std::end(arr));
}

intarr[]={1,2,3};
foo(arr);

std::vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
foo(v);

static_assert and type traits


static_assert performs an assertion check at compiletime. If the assertion is true, nothing happens. If the assertion is false, the compiler displays the specified
error message.

Hide Copy Code


template<typenameT,size_tSize>
classVector
{
static_assert(Size<3,"Sizeistoosmall");
T_points[Size];
};

intmain()
{
Vector<int,16>a1;
Vector<double,2>a2;
return0;
}

Hide Copy Code


errorC2338:Sizeistoosmall
seereferencetoclasstemplateinstantiation'Vector<T,Size>'beingcompiled
with
[
T=double,
Size=2
]

static_assert becomes more useful when used together with type traits. These are a series of classes that provide information about types at compile time. They are
available in the <type_traits> header. There are several categories of classes in this header: helper classes, for creating compiletime constants, type traits classes, to get
type information at compile time, and type transformation classes, for getting new types by applying transformation on existing types.

In the following example function add is supposed to work only with integral types.

Hide Copy Code


template<typenameT1,typenameT2>
autoadd(T1t1,T2t2)>decltype(t1+t2)

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 6/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject
{
returnt1+t2;
}

However, there are no compiler errors if one writes

Hide Copy Code


std::cout<<add(1,3.14)<<std::endl;
std::cout<<add("one",2)<<std::endl;

The program actually prints 4.14 and "e". But if we add some compiletime asserts, both these lines would generate compiler errors.

Hide Copy Code


template<typenameT1,typenameT2>
autoadd(T1t1,T2t2)>decltype(t1+t2)
{
static_assert(std::is_integral<T1>::value,"TypeT1mustbeintegral");
static_assert(std::is_integral<T2>::value,"TypeT2mustbeintegral");

returnt1+t2;
}

Hide Copy Code


errorC2338:TypeT2mustbeintegral
seereferencetofunctiontemplateinstantiation'T2add<int,double>(T1,T2)'beingcompiled
with
[
T2=double,
T1=int
]
errorC2338:TypeT1mustbeintegral
seereferencetofunctiontemplateinstantiation'T1add<constchar*,int>(T1,T2)'beingcompiled
with
[
T1=constchar*,
T2=int
]

Move semantics
This is yet another important and well covered topic from C++11, that one could write a series of articles, not just a paragraph. Therefore I will not get into too many
details, but encourage you to find additional readings, if you're not already familiar with the topic.

C++11 has introduced the concept of rvalue references specified with && to differentiate a reference to an lvalue or an rvalue. An lvalue is an object that has a name,
while an rvalue is an object that does not have a name a temporary object. The move semantics allow modifying rvalues previously considered immutable and
indistinguishable from const T& types.

A C++ class/struct used to have some implicit member functions: default constructor only if another constructor is not explicitly defined and copy constructor, a
destructor and a copy assignment operator. The copy constructor and the copy assignment operator perform a bitwise or shallow copy, i.e. copying the variables
bitwise. That means if you have a class that contains pointers to some objects, they just copy the value of the pointers and not the objects they point to. This might be
OK in some cases, but for many cases you actually want a deepcopy, meaning that you want to copy the objects pointers refer to, and not the values of the pointers. In
this case you have to explicitly write copy constructor and copy assignment operator to perform a deepcopy.

What if the object you initialize or copy from is an rvalue a temporary. You still have to copy its value, but soon after the rvalue goes away. That means an overhead of
operations, including allocations and memory copying that after all, should not be necessary.

Enter the move constructor and move assignment operator. These two special functions take a T&& argument, which is an rvalue. Knowing that fact, they can modify
the object, such as "stealing" the objects their pointers refer to. For instance, a container implementation such as a vector or a queue may have a pointer to an array of
elements. When an object is instantiating from a temporary, instead of allocating another array, copying the values from the temporary, and then deleting the memory
from the temporary when that is destroyed, we just copy the value of the pointer that refers to the allocated array, thus saving an allocation, copying a sequence of
elements, and a later deallocation.

The following example shows a dummy buffer implementation. The buffer is identified by a name just for the sake of showing a point revealed below, has a pointer
wrapper in an std::unique_ptr to an array of elements of type T and variable that tells the size of the array.

Hide Shrink Copy Code

template<typenameT>
classBuffer
{
std::string_name;
size_t_size;
std::unique_ptr<T[]>_buffer;

public:
//defaultconstructor
Buffer():
_size(16),

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 7/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject
_buffer(newT[16])
{}

//constructor
Buffer(conststd::string&name,size_tsize):
_name(name),
_size(size),
_buffer(newT[size])
{}

//copyconstructor
Buffer(constBuffer&copy):
_name(copy._name),
_size(copy._size),
_buffer(newT[copy._size])
{
T*source=copy._buffer.get();
T*dest=_buffer.get();
std::copy(source,source+copy._size,dest);
}

//copyassignmentoperator
Buffer&operator=(constBuffer&copy)
{
if(this!=&copy)
{
_name=copy._name;

if(_size!=copy._size)
{
_buffer=nullptr;
_size=copy._size;
_buffer=_size>0>newT[_size]:nullptr;
}

T*source=copy._buffer.get();
T*dest=_buffer.get();
std::copy(source,source+copy._size,dest);
}

return*this;
}

//moveconstructor
Buffer(Buffer&&temp):
_name(std::move(temp._name)),
_size(temp._size),
_buffer(std::move(temp._buffer))
{
temp._buffer=nullptr;
temp._size=0;
}

//moveassignmentoperator
Buffer&operator=(Buffer&&temp)
{
assert(this!=&temp);//assertifthisisnotatemporary

_buffer=nullptr;
_size=temp._size;
_buffer=std::move(temp._buffer);

_name=std::move(temp._name);

temp._buffer=nullptr;
temp._size=0;

return*this;
}
};

template<typenameT>
Buffer<T>getBuffer(conststd::string&name)
{
Buffer<T>b(name,128);
returnb;
}
intmain()
{
Buffer<int>b1;
Buffer<int>b2("buf2",64);
Buffer<int>b3=b2;
Buffer<int>b4=getBuffer<int>("buf4");
b1=getBuffer<int>("buf5");

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 8/12
5/31/2017 TenC++11FeaturesEveryC++DeveloperShouldUseCodeProject
return0;
}

The default copy constructor and copy assignment operator should look familiar. What's new to C++11 is the move constructor and move assignment operator,
implemented in the spirit of the aforementioned move semantics. If you run this code you'll see that when b4 is constructed, the move constructor is called. Also, when
b1 is assigned a value, the move assignment operator is called. The reason is the value returned by getBuffer() is a temporary, i.e. an rvalue.

You probably noticed the use of std::move in the move constructor, when initializing the name variable and the pointer to the buffer. The name is actually a string, and
std::string also implements move semantics. Same for the std::unique_ptr. However, if we just said _name(temp._name)the copy constructor would
have been called. For _buffer that would not have been even possible because std::unique_ptr does not have a copy constructor. But why wasn't the move
constructor for std::string called in this case? Because even if the object the move constructor for Buffer is called with is an rvalue, inside the constructor it is
actually an lvalue. Why? Because it has a name, "temp" and a named object is an lvalue. To make it again an rvalue and be able to invoke the appropriate move
constructor one must use std::move. This function just turns an lvalue reference into an rvalue reference.

UPDATE: Though the purpose of this example was to show how move constructor and move assignment operator should be implemented, the exact details of an
implementation may vary. An alternative implementation was provided byMember 7805758 in the comments. To be easier to see it I will show it here:

Hide Shrink Copy Code

template<typenameT>
classBuffer
{
std::string_name;
size_t_size;
std::unique_ptr<T[]>_buffer;

public:
//constructor
Buffer(conststd::string&name="",size_tsize=16):
_name(name),
_size(size),
_buffer(size?newT[size]:nullptr)
{}

//copyconstructor
Buffer(constBuffer&copy):
_name(copy._name),
_size(copy._size),
_buffer(copy._size?newT[copy._size]:nullptr)
{
T*source=copy._buffer.get();
T*dest=_buffer.get();
std::copy(source,source+copy._size,dest);
}

//copyassignmentoperator
Buffer&operator=(Buffercopy)
{
swap(*this,copy);
return*this;
}

//moveconstructor
Buffer(Buffer&&temp):Buffer()
{
swap(*this,temp);
}

friendvoidswap(Buffer&first,Buffer&second)noexcept
{
usingstd::swap;
swap(first._name,second._name);
swap(first._size,second._size);
swap(first._buffer,second._buffer);
}
};

Conclusions
There are many more things to say about C++11; this was just one of many possible beginnings. This article presented a series of core language and standard library
features that every C++ developer should use. However, I recommend you additional readings, at least for some of these features.

License
This article, along with any associated source code and files, is licensed under The Code Project Open License CPOL

https://www.codeproject.com/Articles/570638/TenCplusplusFeaturesEveryCplusplusDeveloper 9/12

Você também pode gostar