Each C ++ developers should use C ++ 11 of 10 properties

Source: http://www.ituring.com.cn/article/39533

This article discusses a number of C ++ 11 characteristics of all developers should learn and use, the new C ++ standard, language and standard library are adding a lot of new property, this article will introduce some fur, but I believe there are some features usage should become part of the daily usage of C ++ developers. You may have found a lot of similar features introduced C ++ 11 standard articles, this article can be seen as a collection of those common features described.

table of Contents:

  • auto keyword
  • nullptr keyword
  • Based on the cycle interval
  • Override and final
  • A strongly typed enumeration
  • Smart Pointers
  • Lambdas expression
  • Non-members begin () and end ()
  • macro and type extractor static_assert
  • Move semantics

auto keyword

Before C ++ 11 standard, Auto keywords will be used to identify semantic temporary variables, in the new standard, it becomes another object of two purposes. It is a type of auto placeholder, it will tell the compiler should be inferred from the actual type of the variable initialization equation. When you want a different scope (for example, namespaces, within a function, for loop initialization type) when declaring variables, auto can be used in these situations.

auto i = 42;        // i is an int
auto l = 42LL;      // l is an long long
auto p = new foo(); // p is a foo*

Use the amount of code often means fewer auto (unless you need to type int This is only one word). When you want to traverse the elements in STL containers, think about how you would write iterative code, many old-fashioned way is to use typedef to do, and auto will greatly simplify the process.

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

You should note, auto return type and not as a function, but you can use auto return type to replace the function, of course, in this case, the function must return a value can. auto will not tell the compiler to infer the actual type of the return value, which tells the compiler to look for in the last paragraph of the function return value type. In the following example, the function return value is a value constituted of type T1 and T2 type, then through the + operator decision.

template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
{
   return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

nullptr keyword

0 was null pointer value, this approach has some drawbacks, because it can be converted implicitly to an integer variable. nullptr keyword representative value type std :: nullptr_t, semantically may be understood as a null pointer. nullptr may be implicitly converted to any type of a null pointer, and member function pointers and variable pointers member, but may be converted to BOOL (value to false), but the situation implicit conversion to integer variables no longer exist.

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; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type

For backward compatibility, the value 0 can still be used as a null pointer.

Based on the cycle interval

C ++ 11 features enhanced for statement to better support for through the collection of "foreach" paradigm. In the new version, users can use for to iterate through the C-style array initialization list, as well as all non-members begin () and end the overloaded vessel.

When you just want to get the elements in the collection / array to do something, rather than focus on the index value, the iterator or the element itself, for this form of very useful.

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*e;
}

Override and final

I often find virtual functions in C ++ will cause a lot of problems, because there is no mechanism to identify a mandatory virtual function in a derived class to be rewritten. virtual keyword is not mandatory, this increases to read the code of some difficulties, because you may have to go to the top level of inheritance to confirm that this method is not virtual. I often encourage developers to use the virtual keyword in a derived class, I did the same thing, it can make the code more readable. However, there are some obvious errors still occur, the following code is an 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 we should override B :: f, but the two are not the same signature function, a parameter is short, the other is int, therefore, B :: f is just another and D :: f naming the same function is overloaded instead of rewriting. You might call f by B type of pointer (), and look forward to the output of D :: f, but the printed result is B :: f.

There is another obvious error: parameters are the same, but the function in the base class is const member functions, rather than in the derived class.

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;}
};

Once again, the relationship between these two functions is overloaded and not rewritten, so if you want to call f by B type of pointer (), the program will print out B :: f, instead of D :: f.

Fortunately, there is a way to describe your intentions, two new, special identifier (not a keyword) added to the C ++ 11 in: override, you can be specified in the base class virtual function should be rewriting; final, can be used to specify the function of the derived class does not override base class virtual function. The first example will become:

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;}
};

This code will trigger a compilation error (if you use the override identifier try a second example, would get the same error.):

'D :: f': function has override identifier and any base class does not override a function

On the other hand, if you want a function can never be rewritten (along down the inheritance hierarchy is not to be rewritten), you can identify the function as final, in the base class and the derived class can do . If it is a derived class, you can use the override and final identifier at the same time.

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;}
};

Using the function 'final' statement can not be 'F :: f' rewritten.

A strongly typed enumeration

"Traditional" C ++ enumerated type has some drawbacks: it throws enumerated type member (if enumeration members with the same name in two enumerated type the same code domain in one code section, which can lead to naming conflicts), they will be converted implicitly to an integer, and can not specify the underlying data type enumeration.

By introducing a new enumerated type, these problems in C ++ 11 is resolved, the new enumeration type called a strongly typed enumeration. This type is used to identify the keyword enum class, it never thrown enumeration members in the code domain, it does not implicitly converted to shaping, but also may have a user-specified underlying type (this feature is also added the traditional enumeration type).

enum class Options {None, One, All};
Options o = Options::All;

Smart Pointers

There are plenty of articles introduced smart pointer, so I just want to mention smart pointers and reference counting memory automatically release related things:

  • a unique_ptr : When a memory is not shared ownership time (it does not have copy constructors) may be used, however, it may be converted into another a unique_ptr (a mobile constructor).

  • shared_ptr : When the ownership of a piece of memory that can be shared, they can use (which is why it's called that name).

  • weak_ptr : have a shared_ptr pointing to a managed entity object reference, but did not do any work reference count, which is used to break the circular reference relationship (imagine a relationship tree, the parent node has a child node reference point (shared_ptr), but also it must have a child node of the parent node reference point; if the second reference is a separate reference to a cycle arises, which causes any object can never released).

In other words, auto_ptr is obsolete and should no longer be used.

When to use unique_ptr, when to use shared_ptr, depending on the program memory requirements of ownership, I recommend that you read the discussion here .

The first example below demonstrates the use of unique_ptr, if you want to control objects transferred to another unique_ptr, please use std :: move (I will discuss this function in the last paragraph). After the transfer of control, so that the control of the smart pointer becomes null, if you call 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());

The second example demonstrates the use of shared_ptr. Despite the different semantics, because ownership is shared, but usage are similar.

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());

The first statement is equivalent to this.

auto p3 = std::make_shared<int>(42);

make_shared is a non-member function, memory is allocated with the shared objects, and advantages of the allocated only one memory, and the constructor explicitly initialized shared_ptr comparison, which require at least two memory allocation. These additional costs may cause memory overflow problem, the next example, if the seed () throws an exception, it indicates that a memory overflow.

void foo(std::shared_ptr<int> p, int init)
{
   *p = init;
}
foo(std::shared_ptr<int>(new int(42)), seed());

If you use make_shared, you can avoid similar problems. The third example shows the use of weak_ptr, pay attention, you have to get the shared_ptr in reference to the object by calling the lock (), in order 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;

If you try to call lock on an already expired weak_ptr (weak reference object has been released), you'll get an empty shared_ptr.

Lambdas expression

Anonymous method, also known as lambda expressions, was added to the C ++ 11 standard years, and immediately got the attention of developers. This is a drawing from a functional language to the very powerful features, it makes a number of other features and powerful library can be achieved. In any function objects, functions, local std :: function that appears, you can use lambda expressions, you can here to read the syntax of lambda.

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;

One thing is complicated recursive lambda expression. Imagine a Fibonacci function of the lambda expression represents, if you try to write this auto function, you'll get a compile 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

This problem is due to the auto extrapolates according to the type of object initializer, but initializer but contains a reference to his own expression, therefore, still need to know its type, which is a circulatory problems. To solve this problem, we must break the infinite loop, explicit use std :: function to specify the type of function.

std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};

Non-members begin () and end ()

You may have noticed that I have used non-members begin in the above example () and end () function, which is something new added to the STL, improve the standard and consistency of language, but also to more generic programming becomes possible, and all of them are compatible with STL containers, but not simply overloaded, so you can sprawl begin () and end (), to be compatible with any type, for C heavy-duty type of the array is the same support.

Let us take an example of a written earlier, in this case, I tried to print out a vector, and find it odd value of the first element. If std :: vector in C-style arrays to replace it, so the code might look like the following:

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;

If you are using non-members begin () and end (), the code can be written:

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;

This code is basically the same as std :: vector and use that code, which means that we can all support begin () and end () of the type to write a generic function for this purpose.

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);

macro and type extractor static_assert

static_assert executes a compiler of the assertion, if the assertion is true, nothing will happen, if the assertion is false, the compiler will be some specific error message is displayed.

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
   ]

When used with and the type of extraction, static_assert will become more useful, these are a series can provide extra information at compile the class, they are encapsulated in a header inside the header file, there are a number of classification: with to create a compilation of constant helper class used to obtain the type of extraction compile class type of information, you can convert an existing order type for a new type of class type conversion.

In the following example in that, add function is designed to handle only basic types.

template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
   return t1 + t2;
}

However, if you are so write it, and will not compile errors.

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

Program actually printed 4.14 and "e", but if we add some compiler assert that these two lines of code will produce a compilation error.

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
   ]

Move semantics

This is a very important and 11 topics related to the technical characteristics of a lot of C ++, not just on this topic can write a, better write a series of articles. So, I am here and will not be described too much technical detail, if you are not very familiar with this topic, I would encourage you to read some additional information.

To distinguish between references and references pointing to the right value, C ++ 11 introduces the right reference value (represented by &&) The concept of value pointing to the left. Left value is the name of an object, and the right value is a no-name object (a temporary object). Mobile semantics allow to modify the value of the right (before considering its inflexibility, and thus the concept const T & types of some confusion).

A C ++ class / structure has some implicit member functions: default constructor (if and only if the other constructor is not explicitly defined), copy constructor, a destructor function, and a copy of the assignment operator. Copy constructor and assignment operator copies usually performs a bitwise copy (copy or light), e.g., bitwise copy individually variable. This means that if you have a pointer to an object that contains a class, they will only copy the address pointer, and does not copy the object pointer. Which in some cases is possible, but for the vast majority of cases, all you need is a deep copy of the object is a pointer to the copy, rather than the value of the pointer itself, in which case you have to significantly type write a copy constructor and copy assignment operator to perform a deep copy.

So, if you want to initialize the data source or the right to copy a value type (temporary) What happens? You still have to copy its value, but soon, the value of the right will disappear, which means that some overhead operations, including the allocation of memory, and the last copy of the data, these are unnecessary.

We introduce mobile and mobile constructor assignment operator, these two special function accepts a T && right value of the parameter type, these two functions can modify the object, like the references to the object "stolen" to. As an example, a particular implementation of the container (e.g., vector or queue) may contain a pointer pointing to the array element, we can assign another array space for these elements, copying the data from the temporary space and temporary data when the time to failure delete this memory, we can use this interim data directly instantiated, we just copy the address of a pointer to the array elements, so it saves the overhead once allocated memory, copy a series of elements and freed later overhead.

The following example shows the implementation of a virtual buffer, this buffer is identified by a name (just to explain better), a pointer (std :: unique_ptr with encapsulated), a point type array of T , also has a memory size of the array variable.

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;
}  

The default copy constructor and copy assignment operator should look very similar to the C ++ 11 standard, the new thing is the mobile constructor move semantics and mobile design assignment operator. If you run this code, you will see, when b4 being constructed, the constructor calls the move. When b1 is assigned a value, the movement is called the assignment operator, the reason is getBuffer () Returns the value of the right is a temporary value.

You may have noticed a detail, when the initialization variable name and a pointer to the buffer, we use std :: move in the mobile constructor. name is a variable of type string, std :: string support move semantics, unique_ptr is the same, however, if we use _name (temp._name), the copy constructor will be called, but _buffer, this but it is impossible because std :: unique_ptr not copy constructor, but why std :: string constructor movement is not called in this case? because even if the object is moving Buffer calling the constructor is an rvalue type inside the constructor but is actually a type of value left, and why? Because he has a name "temp", and the name of the object is a type of value left. To make it once again becomes the right value type (also to be the right call mobile constructor), we have to use std :: move. This function behaves just left a reference into a value type of the right type reference values.

Update: While the purpose of this example is to show how to achieve move constructor and move assignment operator, but the specific implementation details may vary, and the other is a method to achieve 7,805,758 members mentioned in the review, in order to so that we more easily see, I write it in the text.

template <typename T>
class Buffer
{
   std::string          _name;
   size_t               _size;
   std::unique_ptr<T[]> _buffer;

public:
   // constructor
   Buffer(const std::string& name = "", size_t size = 16):
      _name(name),
      _size(size),
      _buffer(size? new T[size] : nullptr)
   {}

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

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

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

   friend void swap(Buffer& first, Buffer& second) noexcept
   {
       using std::swap;
       swap(first._name  , second._name);
       swap(first._size  , second._size);
       swap(first._buffer, second._buffer);
   }
};

in conclusion

C ++ 11 contains a lot of content above is just a part of the initial introduction, the article article shows a series of core technology and the use of C ++ standard library features, but I recommend you at least some of them do some extra features, in-depth reading.

出处:Ten C++11 Features Every C++ Developer Should Use

 

Guess you like

Origin blog.csdn.net/smartgps2008/article/details/92070340