auto
nullptr
override
and final
enum
begin()
and end()
static_assert
and property classesauto
keyword was used as a variable storage specifier (such as register, static, extern
). In C ++ 11, auto
allows you to not explicitly specify the type of a variable, telling the compiler to define the actual type of the variable itself, based on the type of the value being initialized. This can be used to declare variables in different scopes, such as namespaces, blocks, loop initialization, and so on. auto i = 42; // i - int auto l = 42LL; // l - long long auto p = new foo(); // p - foo*
auto
allows you to reduce the code (unless, of course, the type is not int
, which is one letter less). Think about the STL iterators that you should always write to pass containers. Thus, this makes obsolete the typedef
definition just for the sake of simplicity. std::map<std::string, std::vector<int>> map; for(auto it = begin(map); it != end(map); ++it) { // do smth } // , ++03 ++11 // C++03 for (std::vector<std::map<int, std::string>>::const_iterator it = container.begin(); it != container.end(); ++it) { // do smth } // C++11 for (auto it = container.begin(); it != container.end(); ++it) { // do smth }
auto
. However, you can use auto
instead of the return type of the function. In this case, auto
does not tell the compiler that it must determine 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 the compose
function is the return type of the + operator, which summarizes values ​​of type T
and E
template <typename T, typename E> auto compose(T a, E b) -> decltype(a+b) // decltype - { return a+b; } auto c = compose(2, 3.14); // c - double
nullptr
has its own std::nullptr_t
, which saves us from past problems. There are implicit conversions of nullptr
to a null pointer of any type and to bool
(as false
), but there is no conversion to integer types. void foo(int* p) {} void bar(std::shared_ptr<int> p) {} int* p1 = NULL; int* p2 = nullptr; if(p1 == p2) {} foo(nullptr); bar(nullptr); bool f = nullptr; int i = nullptr; // : int reinterpret_cast
foreach
paradigm support has been added to iterate over the set. In the new form, it is possible to perform iterations if the begin()
and end()
methods are overloaded for the iteration object. 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(const auto &kvp: map) { std::cout << kvp.first << std::endl; for(auto v: kvp.second) std::cout << v << std::endl; } int arr[] = {1,2,3,4,5}; for(int &e: arr) e *= e;
virtual
optional and therefore made it a bit difficult to read the code, forcing it to always return to the top of the inheritance hierarchy to see if one method or another was declared virtual. I have always used this keyword also in derived classes (and encouraged people who did it) to make the code more understandable. However, there are errors that can still occur. Take the following example: class B { public: virtual void f(short) {std::cout << "B::f" << std::endl;} }; class D : public B { public: virtual void f(int) {std::cout << "D::f" << std::endl;} };
D::f
overrides B::f
. However, they have different signatures, one method accepts short
, the other one - int
, therefore B::f
is just another method with the same name, overloaded, not overdetermined. Thus, working through a pointer to the base class, you can call f()
and wait for the output of the “overridden” method: “D :: f”, but the output will be “B :: f”. class B { public: virtual void f(int) const {std::cout << "B::f " << std::endl;} }; class D : public B { public: virtual void f(int) {std::cout << "D::f" << std::endl;} };
override
to indicate that the method is a virtual method override in the base class and final
indicating that the derived class should not override the virtual method. The first example now looks like this: class B { public: virtual void f(short) {std::cout << "B::f" << std::endl;} }; class D : public B { public: virtual void f(int) override {std::cout << "D::f" << std::endl;} };
override
in the second example): 'D::f': method with override specifier 'override' did not override any base class methods
final
. You can use both identifiers in a derived class at once. class B { public: virtual void f(int) {std::cout << "B::f" << std::endl;} }; class D : public B { public: virtual void f(int) override final {std::cout << "D::f" << std::endl;} }; class F : public D { public: virtual void f(int) override {std::cout << "F::f" << std::endl;} };
final
cannot be overridden by the function F::f()
- in this case, it overrides the base class method (
) for class D
enum class
keyword. They no longer export their enumerated values ​​to the surrounding scope, are no longer implicitly converted to an integer type, and may have a user-defined type (this option is also added for "traditional" enumerations "). enum class Options {None, One, All}; Options o = Options::All;
unique_ptr
shared_ptr
, but does not count links; allows you to get rid of the cyclic dependenceunique_ptr
. To transfer ownership of an object to another unique_ptr
, use std :: move (this function will be discussed in the last paragraph). After the transfer of ownership, the smart pointer that transferred ownership becomes zero and get()
returns nullptr
. void foo(int* p) { std::cout << *p << std::endl; } std::unique_ptr<int> p1(new int(42)); std::unique_ptr<int> p2 = std::move(p1); // transfer ownership if(p1) foo(p1.get()); (*p2)++; if(p2) foo(p2.get());
shared_ptr
. The usage is similar, although the semantics are different, since ownership is now shared. void foo(int* p) { } void bar(std::shared_ptr<int> p) { ++(*p); } std::shared_ptr<int> p1(new int(42)); std::shared_ptr<int> p2 = p1; bar(p1); foo(p2.get());
auto p3 = std::make_shared<int>(42);
shared_ptr
through a constructor, where at least two selections are required. Because of this, a memory leak may occur. The following example demonstrates this; a leak can occur if seed()
throws an exception. void foo(std::shared_ptr<int> p, int init) { *p = init; } foo(std::shared_ptr<int>(new int(42)), seed());
make_shared
.weak_ptr
. Note that you must get shared_ptr
for an object by calling lock()
to access the object. auto p = std::make_shared<int>(42); std::weak_ptr<int> wp = p; { auto sp = wp.lock(); std::cout << *sp << std::endl; } p.reset(); if(wp.expired()) std::cout << "expired" << std::endl;
std::function
is expected. Lambda, generally speaking, is a shorter functor notation, something like an anonymous functor. More details can be read, for example, on MSDN . 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), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;}; auto pos = std::find_if(std::begin(v), std::end(v), is_odd); if(pos != std::end(v)) std::cout << *pos << std::endl;
auto
, you will get a compilation error: auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};
error C3533: 'auto &': a parameter cannot have a type that contains 'auto' error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer error C3536: 'fib': cannot be used before it is initialized error C2064: term does not evaluate to a function taking 1 arguments
std::function
. std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};
begin()
and end()
functions. This is a new addition to the standard library. They work with all STL containers and can be extended to work with any type.std::vector
replaced with a C-like array, the code will look like this: int arr[] = {1,2,3}; std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;}; auto begin = &arr[0]; auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]); auto pos = std::find_if(begin, end, is_odd); if(pos != end) std::cout << *pos << std::endl;
begin()
and end()
it can be rewritten as follows: int arr[] = {1,2,3}; std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;}; auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd); if(pos != std::end(arr)) std::cout << *pos << std::endl;
std::vector
. Thus, we can write one universal method for all types, which are supported by the begin()
and end()
functions. template <typename Iterator> void bar(Iterator begin, Iterator end) { std::for_each(begin, end, [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;}; auto pos = std::find_if(begin, end, is_odd); if(pos != end) std::cout << *pos << std::endl; } template <typename C> void foo(C c) { bar(std::begin(c), std::end(c)); } template <typename T, size_t N> void foo(T(&arr)[N]) { bar(std::begin(arr), std::end(arr)); } int arr[] = {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
verifies assertion at compile time. If the statement is true, then nothing happens. If - false, the compiler displays the specified error message. template <typename T, size_t Size> class Vector { static_assert(Size > 3, "Size is too small"); T _points[Size]; }; int main() { Vector<int, 16> a1; Vector<double, 2> a2; return 0; }
error C2338: Size is too small see reference to class template instantiation 'Vector<T,Size>' being compiled with [ T=double, Size=2 ]
static_assert
becomes more useful when used with property classes. This is a collection of classes that provide type information at compile time. They are available in the <type_traits>
header. There are several types of classes in this header: helper classes, transformation classes, and property classes themselves.add
function is supposed to work only with integer types. template <typename T1, typename T2> auto add(T1 t1, T2 t2) -> decltype(t1 + t2) { return t1 + t2; }
std::cout << add(1, 3.14) << std::endl; std::cout << add("one", 2) << std::endl;
static_assert
, these two lines will cause an error during compilation. template <typename T1, typename T2> auto add(T1 t1, T2 t2) -> decltype(t1 + t2) { static_assert(std::is_integral<T1>::value, "Type T1 must be integral"); static_assert(std::is_integral<T2>::value, "Type T2 must be integral"); return t1 + t2; }
error C2338: Type T2 must be integral see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled with [ T2=double, T1=int ] error C2338: Type T1 must be integral see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled with [ T1=const char *, T2=int ]
std::unique_ptr
) to an array of elements of type T and a variable containing the size of the array. template <typename T> class Buffer { std::string _name; size_t _size; std::unique_ptr<T[]> _buffer; public: // default constructor Buffer(): _size(16), _buffer(new T[16]) {} // constructor Buffer(const std::string& name, size_t size): _name(name), _size(size), _buffer(new T[size]) {} // copy constructor Buffer(const Buffer& copy): _name(copy._name), _size(copy._size), _buffer(new T[copy._size]) { T* source = copy._buffer.get(); T* dest = _buffer.get(); std::copy(source, source + copy._size, dest); } // copy assignment operator Buffer& operator=(const Buffer& copy) { if(this != ©) { _name = copy._name; if(_size != copy._size) { _buffer = nullptr; _size = copy._size; _buffer = (_size > 0)? new T[_size] : nullptr; } T* source = copy._buffer.get(); T* dest = _buffer.get(); std::copy(source, source + copy._size, dest); } return *this; } // move constructor Buffer(Buffer&& temp): _name(std::move(temp._name)), _size(temp._size), _buffer(std::move(temp._buffer)) { temp._buffer = nullptr; temp._size = 0; } // move assignment operator Buffer& operator=(Buffer&& temp) { assert(this != &temp); // assert if this is not a temporary _buffer = nullptr; _size = temp._size; _buffer = std::move(temp._buffer); _name = std::move(temp._name); temp._buffer = nullptr; temp._size = 0; return *this; } }; template <typename T> Buffer<T> getBuffer(const std::string& name) { Buffer<T> b(name, 128); return b; } int main() { Buffer<int> b1; Buffer<int> b2("buf2", 64); Buffer<int> b3 = b2; Buffer<int> b4 = getBuffer<int>("buf4"); b1 = getBuffer<int>("buf5"); return 0; }
b4
is created, the move constructor is called. In addition, when b1
assigned a value, the assignment operator is called. The reason for this is the value returned by the getBuffer()
function - rvalue.std::string
and std::string
also implements move semantics. The same goes for unique_ptr
. However, if we wrote just _name(temp._name)
, the copy constructor would be called. But why in this case the displacement constructor for std::string
was not called? The fact is that even if the displacement constructor for Buffer
was called with an rvalue, inside the constructor it is still represented as an lvalue. To make it rvalue again and need to use std::move
. This function simply turns the lvalue reference into an rvalue.Source: https://habr.com/ru/post/182920/
All Articles