C++11 new features combing

In interviews, a question that is often asked is: What new features do you know about C++11? Generally speaking, it is enough to answer the following four aspects:

  • "Syntax sugar": nullptr, autoautomatic type deduction, range for loops, initialization lists, lambda expressions, etc.
  • rvalue references and move semantics
  • smart pointer
  • Multithreaded programming threadin C++11: Libraries and their accompanying synchronization primitives mutex, lock_guard, condition_variable, and asynchronystd::furture

1. "Syntax sugar"

This part of the content is generally carried in one sentence, but sometimes it needs to be said, the more important ones are auto and lambda.

autoAutomatic type deduction

The C language also has the auto keyword, but its meaning is only to distinguish it from static variables. If a variable is not specified, the default is auto. . Because few people use this thing, the original auto function was abandoned in C++11 and became the current automatic type deduction keyword. The usage is very simple and won't go into details. For example, if you write an auto a = 3, the compiler will automatically deduce the type of a as int. When traversing some STL containers, you don't need to declare the type of those iterators, and you don't need to use typedef It can be very simple to implement traversal.
The use of auto has the following two points that must be noted:

  • Variables declared by auto must be initialized, otherwise the compiler cannot determine the type of the variable.
  • auto cannot be declared as a return value, auto cannot be used as a formal parameter, and auto cannot be modified as a template parameter

Regarding efficiency : auto actually deduces the type of variables at compile time, so it will not adversely affect the efficiency of the program. In addition, auto does not affect the compilation speed, because the right side should be deduced at compile time and then judge whether it matches the left side.
For specific derivation rules, you can refer to here

lambda expression

A lambda expression is an anonymous function, which can be considered as an executable functor. The syntax rules are as follows:

[捕获区](参数区){代码区};

Such as

auto add = [](int a, int b) {return a + b};

As far as I understand it, capture means to expand some variables to make them visible inside the lambda. The specific ways are as follows

  • [a,&b]  where  a  is captured by copy and  b  is captured by reference.
  • [this]  captures the current object by reference (  *this )
  • [&] captures by reference all automatic variables used in the body of the lambda , and captures the current object by reference, if any
  • [=] captures all automatic variables  used in the body of the lambda by copy , and captures the current object by reference, if any
  • []  do not capture, in most cases not capture is fine

General usage scenarios : custom comparison functions such as sort, and simple threads with thread.

2. rvalue references and move semantics

Rvalue reference is a new feature of C++11. It realizes transfer semantics and perfect forwarding. The main purpose has two aspects.

  • Eliminate unnecessary object copies when two objects interact, save computing and storage resources, and improve efficiency
  • Able to more concisely and explicitly define
    a variable in a generic function C++ is either an lvalue or an rvalue. The popular definition of lvalue refers to a non-temporary variable, while an lvalue refers to a temporary object. The symbol for an lvalue reference is one &, and the symbol for an rvalue reference is two &&

Move Semantics Move
semantics can move resources (heap, system objects, etc.) from one object to another, which can reduce unnecessary creation, copying, and destruction of temporary objects. Move semantics and copy semantics are relative and can be compared to file cutting and copying. In the existing C++ mechanism, to implement transfer semantics, a custom class needs to define a move constructor and also a transfer assignment operator.
Take the move constructor of the string class as an example

MyString(MyString&& str) {
    std::cout << "Move Ctor source from " << str._data << endl;
    _len = str._len;
    _data = str._data;
    str._len = 0;
    str._data = NULL;
}

Similar to the copy constructor, there are a few things to note:

  1. Argument (rvalue) symbol must be &&
  2. The parameter (rvalue) cannot be a constant, because we need to modify the rvalue
  3. The resource link and tag of the parameter (rvalue) must be modified, otherwise, the rvalue's destructor will free the resource. The resources transferred to the new object are also invalid.

Standard library function std::move --- turns an lvalue into an rvalue

The compiler can only call the move constructor for rvalue references, so if it is known that a named object is no longer used, it still wants to call its move constructor, that is, using an lvalue reference as an rvalue reference , how should I do it? With std::move, this function converts an lvalue reference to an rvalue reference in a very simple way.

Perfect Forwarding

Perfect forwarding uses scenarios where you need to pass a set of arguments to another function unchanged. Intact not only does the value of the parameter remain unchanged, but also has the following two sets of properties in C++:

  • lvalue/rvalue
  • const/non-const
    perfect forwarding is that all these properties and parameter values ​​cannot be changed during parameter passing. In generic functions, such a need is very common.
    In order to ensure these properties, generic functions need to overload various versions, different versions of lvalue and rvalue, and also correspond to different const relationships, but if only one function version of rvalue reference parameter is defined, this problem is easily solved. The reason is:
    C++11's type deduction for T&&: rvalue arguments are rvalue references, and lvalue arguments are still lvalues

3. Smart pointers

Core idea: In order to prevent problems such as memory leaks, an object is used to manage wild pointers, so that the pointer management right is obtained when the object is constructed, and it is automatically released (delete) when it is destructed.
Based on this idea, C++98 provides the first A smart pointer: auto_ptr
auto_ptrBased on the semantics of ownership transfer, when an existing auto_ptr is assigned to another new auto_ptr, the old one no longer has control of the pointer (the internal pointer is assigned to null), then this will Brings some fundamental flaws:

  • When the function parameter is passed, there will be an implicit assignment, then the original auto_ptr automatically loses control
  • When self-assignment, it will assign its own internal pointer to null, causing a bug

Because of various bugs in auto_ptr, the C++11 standard basically abandoned this type of smart pointer, and instead brought three new smart pointers:

  • shared_ptr , a smart pointer based on reference count, will count how many objects currently own the internal pointer at the same time; when the reference count drops to 0, it will be automatically released
  • weak_ptr , smart pointers based on reference counting will be powerless in the face of circular references, so C++11 also introduces weak_ptr to be used with it, weak_ptr is only referenced, not counted
  • unique_ptr : A smart pointer that follows exclusive semantics. At any point in time, resource intelligence is uniquely occupied by a unique_ptr, which is automatically destructed when it leaves the scope. The transfer of resource ownership can only be done by std::move()assignment and not by assignment

Demonstrating breadth of knowledge: Garbage collection mechanisms in languages ​​such as Java The
garbage collector treats memory as a directed reachable graph whose nodes are divided into a set of root nodes and a set of heap nodes. Each heap node corresponds to a memory allocation block. When there is a directed path from any root node to a certain heap node p, we say that node p is reachable. At any time, unreachable nodes are garbage. The garbage collector maintains this graph and periodically reclaims unreachable nodes by periodically releasing them and returning them to the free list.
Therefore, we can also extend the concepts of malloc's allocation mechanism, partner system, virtual memory, etc.

Here is a simple implementation of shared_ptr:

class Counter {
    friend class SmartPointPro;
public:
    Counter(){
        ptr = NULL;
        cnt = 0;
    }
    Counter(Object* p){
        ptr = p;
        cnt = 1;
    }
    ~Counter(){
        delete ptr;
    }

private:
    Object* ptr;
    int cnt;
};

class SmartPointPro {
public:
    SmartPointerPro(Object* p){
        ptr_counter = new Counter(p);
    }
    SmartPointerPro(const SmartPointerPro &sp){
        ptr_counter = sp.ptr_counter;
        ++ptr_counter->cnt;
    }
    SmartPointerPro& operator=(const SmartPointerPro &sp){
        ++sp.ptr_counter->cnt;
        --ptr_counter.cnt;
        if(ptr_counter.cnt == 0)
            delete ptr_counter;
        ptr_counter = sp.ptr_counter;
    }
    ~SmartPointerPro(){
        --ptr_counter->cnt;
        if(ptr_counter.cnt == 0)
            delete ptr_counter;
    }
private:
    Counter *ptr_counter;
};

Something to keep in mind, there are three situations that can cause a reference count to change:

  1. When calling the constructor: SmartPointer p(new Object());
  2. When assigning a constructor: SmartPointer p(const SmartPointer &p);
  3. When assigning: SmartPointer p1(new Object()); SmartPointer p2 = p1;

C++11 multithreaded programming

thread#include <thread>

std::thread can be used with normal functions and lambda expressions. It also allows arbitrarily many arguments to be passed to the thread execution function.

#include <thread>
void func() {
 // do some work here
}
int main() {
   std::thread thr(func);
   t.join();
   return 0;
} 

The above is the simplest example of using std::thread, the function func() is executed in the newly started thread. The join() function is called to block the main thread until the newly started thread finishes executing. The return value of the thread function is ignored, but the thread function can accept any number of input parameters.

std::threadother member functions of

  • joinable() : Determines whether the thread object can be joined. When the thread object is destructed, if the object ` joinable()==truewill std::terminatebe called.
  • join() : Block the current process (usually the main thread), waiting for the new thread created to be reclaimed by the operating system after execution.
  • detach() : Detaches the thread, from which the thread object is governed by the operating system.

thread management function

In addition to std::threadthe member functions, std::this_threada series of functions are also defined in the namespace to manage the current thread.

Function name effect
get_id Returns the id of the current thread
yield Tells the scheduler to run other threads, available for the currently busy wait state. Equivalent to actively giving up the remaining execution time, the specific scheduling algorithm depends on the implementation
sleep_for Stop the execution of the current thread for a specified period of time
sleep_until Stop the execution of the current thread until the specified point in time

As for the use of synchronization primitives such as mutex, condition_variable, and the future keyword, I will not introduce it in detail here. If you have used it, you can say it naturally. If you have not used it, this part should not be discussed with the interviewer.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325187626&siteId=291194637