Summary of new features of C++

(Smart pointers, some keywords, automatic type deduction auto, perfect forwarding of rvalue reference move semantics, list initialization, std::function & std::bind & lambda expressions make callbacks more convenient, c++11 introduces concurrency A lot of good things, including:
std::thread related
std::mutex related
std::lock related
std::atomic related
std::call_once related
volatile related
std::condition_variable related
std::future related
async related)

Summarize three aspects: one is more convenient, and the other is more efficient and safe . Convenient keywords such as auto, for i:arr, std::function. Effective such as smart pointers, perfect forwarding with move semantics.
The third is thread support. Concurrency support. Atomic variables, locks, condition variables, etc. Asynchronously obtain the result future, etc.,

9. Smart pointer problem
A smart pointer is a class used to store pointers to dynamically allocated objects and is responsible for automatically releasing dynamically allocated objects to prevent heap memory leaks. Dynamically allocated resources are handed over to a class object for management. When the declaration cycle of the class object ends, the destructor is automatically called to release the resource.
Although the use of new and delete operators for dynamic memory management can improve the efficiency of the program, it is also very prone to problems: forgetting to release the memory, causing memory leaks, releasing it when there is still a pointer referencing the memory, resulting in illegal references The memory pointer, the program enters the catch after an exception occurs and forgets to release the memory. Smart pointers are encapsulation of ordinary pointers using RAII technology. RAII technology, also known as " resource acquisition is initialization ", is a C++ language idiom for managing resources and avoiding leaks. Use local objects (classes) stored on the stack to encapsulate resource allocation and initialization, complete resource allocation and initialization in the constructor, and complete resource cleanup in the destructor , which can ensure correct initialization and resource release. A local object refers to an object stored on the stack, and its life cycle is managed by the operating system without manual intervention.
The smart pointers provided after the C++11 version are included in the header file, which are auto_ptr, shared_ptr, unique_ptr, and weak_ptr
smart pointer code implementation: Use two classes to realize the function of smart pointers**, one is the reference counting class, and the other is One is a pointer class. **

//  引用计数器类  用于存储指向同一对象的指针数
template<typename T>
class Counter
{
    
    
private:
	//  数据成员
	T *ptr;    //  对象指针
	int cnt;   //  引用计数器
 
	//  友元类声明
	template<typename T>
	friend class SmartPtr;
 
	//  成员函数
	//  构造函数
	Counter(T *p)   //  p为指向动态分配对象的指针
	{
    
    
		ptr = p;
		cnt = 1;
	}
	//  析构函数
	~Counter()
	{
    
    
		delete ptr;
	}
};
//  智能指针类  
template<typename T>
class SmartPtr
{
    
    
private:
	//  数据成员
	Counter<T> *ptr_cnt;  //  
 
public:
 
	//  成员函数
	//  普通构造函数  初始化计数类
	SmartPtr(T *p)
	{
    
    
		ptr_cnt = new Counter<T>(p);
	}
	//  拷贝构造函数 计数器加1
	SmartPtr(const SmartPtr &other)
	{
    
    
		ptr_cnt = other.ptr_cnt;
		ptr_cnt->cnt++;
	}
	//  赋值运算符重载函数
	SmartPtr &operator=(const SmartPtr &rhs)
	{
    
    
		ptr_cnt = rhs->ptr_cnt;
		rhs.ptr_cnt->cnt++; 增加右操作数的计数器
		ptr_cnt->cnt--; 左操作数计数器减1
		if (ptr_cnt->cnt == 0)
			delete ptr_cnt;
		return *this;
	}
	//  解引用运算符重载函数
	T &operator*()
	{
    
    
		return *(ptr_cnt->cnt);
	}
 
	//  析构函数
	~SmartPtr()
	{
    
    
		ptr_cnt->cnt--;
		if (ptr_cnt->cnt == 0)
			delete ptr_cnt;
		else
			cout << "还有" << ptr_cnt->cnt << "个指针指向基础对象" << endl;
	}
};

shared_ptr: The reference counter method is used to allow multiple smart pointers to point to the same object. Whenever one more pointer points to the object, the internal reference counts of all smart pointers pointing to the object are increased by 1, and each time a smart pointer is reduced to point to the object , the reference count will be decremented by 1, and when the count is 0, the dynamically allocated resources will be released automatically.
shared_ptr initialization:


std::shared_ptr<T> sp; //空shared_ptr,可以指向类型为T的对象
 
std::shared_ptr<int> sp(new int(5)); //指定类型,传入指针通过构造函数初始化
 
std::shared_ptr<int> sp = std::make_shared<int>(5); //使用make_shared函数初始化
 
//智能指针是一个模板类,不能将一个原始指针直接赋值给一个智能指针,因为一个是类,一个是指针
std::shared_ptr<int> sp = new int(1); //error

unique_ptr unique_ptr uniquely owns the object it refers to, and there can only be one unique_ptr pointing to a given object at a time. Transferring a unique_ptr will transfer all ownership from the source pointer to the target pointer, and the source pointer will be emptied; so unique_ptr does not support ordinary copy and assignment operations, and cannot be used in STL standard containers ; except for the return value of local variables (because the compiler The device knows that the object to be returned will be destroyed); if you copy a unique_ptr, then after the copy is over, the two unique_ptr will point to the same resource, causing multiple releases of the same memory pointer at the end and causing the program to crash.
Initialization: There is no make_shared function, only pointers can be passed in via new.

std::unique_ptr<T> up; //空unique_ptr,可以指向类型为T的对象,up会使用delete来释放它的指针
 
std::unique_ptr<int> up(new int(5)); //绑定动态对象

unique_ptr has no copy constructor and does not support ordinary copy and assignment operations; but it provides a movement mechanism to transfer the ownership of the pointer from one unique_ptr to another unique_ptr (using the std::move function, you can also call release or reset )

std::unique_ptr<int> upMove = std::move(up); //转移所有权

std::unique_ptr<int> up1(new int(5));
std::unique_ptr<int> up2(up1.release()); //up2被初始化为up1原来保存的指针,且up1置为空
std::unique_ptr<int> up3(new int(6));
up2.reset(up3.release()); //reset释放了up2原来指向的内存,指向up3原来保存的指针,且将up3置为空

Unique_ptr has a wide range of applications. It can return the ownership of dynamic application resources within the function; it can save pointers in the container; it supports the management of dynamic arrays .

weak_ptr is a weak reference pointer, which comes with shared_ptr and does not have the behavior of ordinary pointers. There are no overloaded * and -> operators in the template class, which means that weak_ptr type pointers can only access the pointed of heap memory without being able to modify it. . It mainly solves the problem of shared_ptr reference counting: it will cause memory leaks when circular references are made .
weak_ptr points to an object managed by shared_ptr, binding a weak_ptr to a shared_ptr does not change the reference count of shared_ptr. If a piece of memory is referenced by shared_ptr and weak_ptr at the same time, when all shared_ptr are destroyed, no matter whether there is still weak_ptr referencing the memory, the memory will be released . So weak_ptr does not guarantee that the memory it points to must be valid. Use the function lock() to check whether weak_ptr is a null pointer before using it.
use_count() View the number of shared_ptr pointers pointing to the same as the current weak_ptr pointer.
expired() Determine whether the current weak_ptr pointer is expired (the pointer is empty, or the pointed heap memory has been released) lock()
If the current weak_ptr has expired, the function will return an empty shared_ptr pointer; otherwise, the function will return a It points to the same shared_ptr pointer as the current weak_ptr.
Circular reference: A calls B, and B calls A, so that the counts of A and B are 1 during initialization, plus 1 during assignment, and decremented by 1 when destructed. Finally, it is still 1, and the resource is not released.
insert image description here
Just change any member variable of A or B to weak_ptr:

Do not return the this pointer as a shared_ptr, because the this pointer is essentially a raw pointer. Therefore, returning this may lead to repeated destruction:
the correct way to return the shared_ptr of this is: let the target class pass the std::enable_shared_from_this class, and then use the member function shared_from_this() of the base class to return the shared_ptr of this:

Are smart pointers thread safe?
Conclusion: The same shared_ptr is safe to be read by multiple threads; the same shared_ptr is not safe to be written by multiple threads; different shared_ptr with shared reference counts is safe to be "written" by multiple threads.
The reason is that shared_ptr is actually composed of pointers to objects and counters. Counter addition and subtraction operations are atomic operations, so this part is thread-safe, but pointers to objects are not thread-safe. For example, the assignment copy of a smart pointer first copies the pointer to the object, and then adds and subtracts the number of references. Although the addition and subtraction of the number of references is an atomic operation, the two-step operation of copying the pointer and the number of references is not an atomic operation, and the thread is not safe. Manual lock and unlock.

auto automatic type deduction (in previous versions, the auto keyword was used to indicate the storage type of the variable, which is relative to the static keyword. a auto means that the variable is automatically stored , which is also the default rule of the compiler, so write It's the same if you don't write it, and generally we don't write it, which makes the existence of the auto keyword very tasteless.)
A typical application scenario of auto is to define the iterator of stl. The iterators of different containers have different types, which must be specified when defining the iterator. The type of iterator is sometimes complicated and cumbersome to write; auto is used for generic programming, when you do not want to specify a specific type, such as in generic programming.
There is also a decltype that is very similar to auto, and it is also an automatic type deduction.
decltype (exp) varname [= value] The parentheses mean that it can be omitted. The difference is that it deduces the type according to the exp expression , and the expression can be simple or a function. Because auto must be initialized, but decltype does not. Auto cannot be used for non-static member variables of a class (also because it is not initialized), but decltype can.

C++11 rvalue reference Rvalue reference is just a new C++ syntax. What is really difficult to understand is the two C++ programming techniques derived from rvalue references, namely move semantics and perfect forwarding .
In C++ or C language, an expression (which can be a literal, variable, object, return value of a function, etc.) is divided into lvalue expressions and rvalue expressions according to different usage scenarios. To be precise, the concept of lvalue and rvalue in C++ is inherited from C language.
1 An expression that can be on the left side of an assignment sign (=) is an lvalue; conversely, an expression that can only be on the right side of an assignment sign is an rvalue. For example:
int a = 5; 5 = a; //Error, 5 cannot be an lvalue.
An lvalue in C++ can also be used as an rvalue, for example:
int b = 10; // b is an lvalue
a = b; // a and b are both lvalues, but b can be used as an rvalue

2 An expression that has a name and can obtain the storage address is an lvalue; otherwise, it is an rvalue. a and b are variable names, and their storage addresses can be obtained through &a and &b, so a and b are both lvalues; on the contrary, literals 5 and 10 have neither names nor their storage addresses (literals Usually stored in a register, or stored with the code), so 5, 10 are rvalues.
Simply put, lvalues ​​are variables and rvalues ​​are literal constants.

Quote, use "&" said. But this kind of reference method has a defect, that is, under normal circumstances, only lvalues ​​in C++ can be operated, and rvalues ​​cannot be referenced. For example:
int num = 10;
int &b = num; //correct
int &c = 10; //error
For this reason, the C++11 standard introduces another reference method, called rvalue reference, with " &&" express.
int && a = 10;
a = 100;
cout << a << endl; (but this kind of constant rvalue reference is meaningless, referencing an unmodifiable constant, because it can be done by constant lvalue reference)
very Quantitative rvalue references can achieve move semantics and perfect forwarding. The problem solved by
move semantics
: when copying an object, if the object member has a pointer, it is a deep copy method (for shallow copy, the problem of multiple destruction), deep copy The memory resources pointed to by the pointer will be copied together. If the pointer members in the temporary object apply for a large amount of heap space, the efficiency is very low.
The so-called move semantics refers to the initialization of class objects containing pointer members by moving instead of deep copying. Simply understood, move semantics refers to "moving to used" memory resources owned by other objects (usually temporary objects ). std::move does not move anything, its only function is to cast an lvalue to an rvalue reference, and then the value can be used through an rvalue reference for move semantics. Can also be used with unique_ptr to transfer ownership.

Implementation method: manually added a constructor for it. Unlike other constructors, this constructor uses parameters in the form of rvalue references , also known as move constructors. And in this constructor, the num pointer variable adopts a shallow copy copy method (reference is a shallow copy), and at the same time reset the original pointer to NULL inside the function, effectively avoiding "the same block is released multiple times" the occurrence of the situation.
We know that non-const rvalue references can only operate on rvalues, and temporary objects generated in program execution results (such as function return values, lambda expressions, etc.) have neither names nor access to their storage addresses, so they are rvalues . When a class contains both a copy constructor and a move constructor, if a temporary object is used to initialize an object of the current class, the compiler will give priority to calling the move constructor to complete this operation.

The concept of perfect forwarding is only useful for template programming and when you are very particular about function performance.
First, we must understand the situation of reference folding :
reference folding is a concept in template programming, which is to solve the situation of double references (as shown below) after template deduction.
Suppose a template function is defined as follows:
template
void PrintType(T&& param){ ... }
When T is int & type, then param is deduced as int & && type, and C++ syntax does not allow this kind of double reference type to exist (you can Define a double-reference variable by yourself, the compiler will prompt an error), so the rules for reference folding have been formulated. The specific rules are as follows: When there is a double reference in parameter type deduction in
template programming**, the double reference will be folded into one reference **, either an lvalue reference or an rvalue reference. The folding rule is: if either reference is an lvalue reference, the result is an lvalue reference. Otherwise (that is, both are rvalue references), the result is an rvalue reference.

Perfect forwarding: perfect forwarding, which means that a function template can "perfectly" forward its own parameters to other functions called internally . The so-called perfection means that it can not only forward the value of the parameter accurately, but also ensure that the left and right value attributes of the forwarded parameter remain unchanged. Therefore, whether perfect forwarding is achieved in many scenarios directly determines whether the parameter passing process uses copy semantics (calling the copy constructor) or move semantics (calling the move constructor). How to achieve
perfect forwarding?
In general, when defining a template function, we use the grammatical format of an rvalue reference to define the parameter type , so that the function can receive either an lvalue passed in from the outside world or an rvalue (otherwise it will only be regarded as an lvalue , because of the rules of reference folding); secondly, it is also necessary to use the forword() template function provided by the C++11 standard library to modify the parameters in the called function that need to maintain left and right value attributes, and forward is uniformly received in the form of an lvalue reference parameterparam . This makes it easy to achieve perfect forwarding of parameters in function templates. (The advantage is similar to a state machine, the initial state is the same, and the intermediate process is the same, and the final result is the same)

List initialization (uniformity of initialization)
In C++11, you can directly add an initialization list after the variable name to initialize the object. Before C++11, there are mainly the following initialization methods: parentheses, equal signs. The initializer list for the constructor. So many object initialization methods not only increase the learning cost, but also make the code style quite different, which affects the readability and uniformity of the code.

28 std::function implements the callback mechanism to understand
what is a callback function: if you pass the function pointer (address) as a parameter to another function, when this pointer is used to call the function it points to, we say this is the callback function. The callback function is not directly called by the implementer of the function, but is called by another party when a specific event or condition occurs, and is used to respond to the event or condition.
Callback: Decoupling leads to: Efficient and flexible The encapsulation of modern C++ leads to a certain degree of independence between modules. But the modules need to cooperate with each other, so it is
more intuitive to have a callback: Suppose A is the core team (A provides a funA function for B to call, it can be regarded as a library function, and there is a sell function that must wait for funA to return the result. Execution), B is another team.
If funA takes a long time to execute, if no callback is used, then A must wait for B to finish executing before executing the next sell(), which is a waste of time.
Callback is funA(sell). Although the sell callback function is defined by A, it is called by B. After calling, the result is returned to A directly, and A does not need to wait. This is efficient
and flexible: ** If you write the sell function directly into the funA function, everything will be fine. However, funA does not know what to do later, only the caller knows. So if different people call it, you only need to pass in different function pointers.

Thinking: Why use it?
But applying function pointers to callback functions embodies a strategy for solving problems and an idea for designing systems. Before explaining this idea, I would like to explain that the callback function can certainly solve some system architecture problems, but it must not be everywhere in the system. If you find that your system is full of callback functions, then you must refactor your system. The callback function itself is a design idea that destroys the system structure,
that is to say, the essence of the callback function is "only we B know what to do, but we don't know when to do it, only other modules A know, so we must Encapsulate what we know into a callback function to tell other modules”

A function pointer can be an ordinary function or a static function of a class.
It can also be a non-static function of a class.

Why use it?
In the era of C language, we can use function pointers to pass a function as a parameter, so that we can implement the callback function mechanism. After C++11, the std::function template class was introduced in the standard library. This template summarizes the concept of function pointers.
std::function<int(int a, int b)> plusFunc;
it can represent any return value is an int, and the formal parameter list is a function pointer such as int a, int b.
int puls(int a, int b)

{ return a + b; } // The function name represents the address of the function, that is, the pointer to the function plusFunc = plus; it has the same form as a callable object. A callable object is not just a function pointer, it can also be a lambda expression, a member function pointer of a class, etc. When std::function is filled with the appropriate parameter list and return value, it becomes a function wrapper that can accommodate all such calling methods. If std::function does not contain a target, it is said to be empty, and calling the target of an empty std::function will result in a std::bad_function_call exception.




std::bind usually has two functions:
bind the callable object and parameters together as another std::function for calling (like function)
only bind some parameters, and reduce the parameters passed in by the callable object . To bind a normal function with placeholder
std::bind
double callableFunc (double x, double y) {return x/y;}
auto NewCallable = std::bind (callableFunc, std::placeholders::_1,2);
std::cout << NewCallable (10) << '\n';
The first parameter of bind is the function name. When an ordinary function is used as an actual parameter, it will be implicitly converted into a function pointer. Therefore std::bind(callableFunc,_1,2) is equivalent to std::bind (&callableFunc,_1,2);
_1 represents a placeholder, located in, std::placeholders::_1;
the first parameter is occupied Bit occupancy means that this parameter is subject to the parameter passed in when calling. When calling NewCallable here, 10 is passed in to it, which is actually equivalent to calling callableFunc(10,2);

Lambda expressions are shorter, can capture contextual data, and return value definitions can be omitted. not very useful,

Concurrency-related
std::thread implements multithreading.
Before c++11, you may use pthread_xxx to create threads, which is cumbersome and unreadable. C++11 introduces std::thread to create threads.
std::thread t(func); c++11 also provides functions such as obtaining the thread id, or the number of system cpus, obtaining thread native_handle, and making the thread sleep; std::thread t(func); cout <<
"
current Thread ID " << t.get_id() << endl;
cout << "current cpu number" << std::thread::hardware_concurrency() << endl;
auto handle = t.native_handle();// handle Can be used for pthread related operations
std::this_thread::sleep_for(std::chrono::seconds(1));

std::Mutex is mainly divided into mutual exclusion with timeout and no timeout, and mutual exclusion without recursion.
C++11 mainly includes std::lock_guard, RAII lock encapsulation, which can dynamically release lock resources and prevent threads from holding locks due to coding errors.

A condition variable is a synchronization mechanism introduced by c++11. It can block a thread or threads until a thread notification or timeout will wake up the blocked thread. The condition variable needs to be used in conjunction with the lock. The lock here is the above Introducing std::unique_lock.

std::future is more advanced than std::thread, std::future is used as a transmission channel for asynchronous results, the return value of the thread function can be easily obtained through get(), std::promise is used to wrap a value, and the data Bind with future, and std::packaged_task is used to wrap a call object, bind the function and future, and facilitate asynchronous calls. The std::future cannot be copied. If you need to copy it into the container, you can use std::shared_future.

std::future we want to return the asynchronous task result
from the thread (that is, the execution of task A must depend on the return value of task B), generally we need to rely on global variables; from a security point of view, it is not appropriate; for this reason, C+ +11 Provides the std::future class template, the future object provides a mechanism for accessing the results of asynchronous operations, and it is easy to solve the problem of returning results from asynchronous tasks. Simply put, std::future can be used to obtain the results of asynchronous tasks , so it can be regarded as a simple means of synchronization between threads. std::future is usually created by a Provider . You can imagine a Provider as a provider of an asynchronous task. The Provider sets the value of the shared state in a thread, and the std::future object associated with the shared state calls get (Usually in another thread) Get the value, if the flag of the shared state is not ready, calling std::future::get will block the current caller until the Provider sets the value of the shared state (at this time the shared state The flag becomes ready), std::future::get returns the value of the asynchronous task or an exception (if an exception occurs) Provider can be a function or a std::async-like function, and the std::async() function will be introduced later in this article . std::promise::get_future, get_future is a member function of the promise class std::packaged_task::get_future, at this time get_future is a member function of packaged_task






std::future is generally created by std::async, std::promise::get_future, std::packaged_task::get_future, but constructors are also provided. The copy constructor of td::future is disabled, only the default constructor and move constructor (moving constructor, using rvalue as parameter) std::future::valid() checks the current
std
:: Whether the future object is valid, i.e. release is associated with some shared state. A valid std::future object can only be initialized via std::async(), std::future::get_future or std::packaged_task::get_future. In addition, the std::future object created by the default constructor of std::future is invalid (invalid). Of course, the std::future object can also become valid after the move assignment of std::future.
std::future::wait()
waits for the flag of the shared state associated with the current std::future object to become ready.
If the flag of the shared state is not ready (the Provider does not set a value (or an exception) on the shared state at this time) ), calling this function will block the current thread until the flag of the shared state becomes ready. Once the flag of the shared state becomes ready, the wait() function returns and the current thread is unblocked, but wait() does not read the shared state value or exception. (This is the difference from std::future::get())
std::future::wait_for() can set a time period rel_time
std::shared_future is similar to std::future, but std::shared_future can be copied, and multiple std::shared_future can share a shared state. The final result
parameter is a future, and use this future to wait for an int type product : std::future& fut
Use the get() method in the child thread to wait for a future future and return a result. The producer uses the async method to do production work and returns a future, and the consumer uses the get() method in the future to obtain the product.

C++14, 17 will not be introduced in detail. C++20 has more coroutines.

auto automatic type deduction (in previous versions, the auto keyword was used to indicate the storage type of the variable, which is relative to the static keyword. a auto means that the variable is automatically stored , which is also the default rule of the compiler, so write It's the same if you don't write it, and generally we don't write it, which makes the existence of the auto keyword very tasteless.)
A typical application scenario of auto is to define the iterator of stl. The iterators of different containers have different types, which must be specified when defining the iterator. The type of iterator is sometimes complicated and cumbersome to write; auto is used for generic programming, when you do not want to specify a specific type, such as in generic programming.
There is also a decltype that is very similar to auto, and it is also an automatic type deduction.
decltype (exp) varname [= value] The parentheses mean that it can be omitted. The difference is that it deduces the type according to the exp expression , and the expression can be simple or a function. Because auto must be initialized, but decltype does not. Auto cannot be used for non-static member variables of a class (also because it is not initialized), but decltype can.

final keyword, so that the class cannot be inherited, the function cannot be rewritten override prompts the virtual function to rewrite
default : most of the time it is used to declare the constructor as the default constructor, if there is a custom constructor in the class, the compiler will The default constructor will not be implicitly generated; and sometimes we want to prohibit the copying and assignment of objects, we can use delete modification, the delele function is very commonly used in C++11, std::unique_ptr is to prohibit the copying of objects through delete modification of.

explicit is specially used to modify the constructor, which means that it can only be constructed explicitly, and cannot be implicitly converted (for a parameter). const
only means the semantics of read only, which only guarantees that it cannot be modified at runtime, but it is still modified It may be a dynamic variable, and the constexpr modified is the real constant, which will be calculated during compilation and cannot be changed during the entire running process. constexpr can be used to modify functions, and the return value of this function will be is computed as a constant at compile time whenever possible

The biggest feature of tuple tuple is: the instantiated object can store any amount of data of any type.
C++11 long long super long shaping
C++11 shared_ptr, unique_ptr, weak_ptr smart pointers See the front for details

There are also some new algorithms, all of any of, none of

Inherited constructor: Because if there are many constructors of the base class, the derived class must rewrite the constructor of the base class, you can use using base::base, no need to rewrite.

Guess you like

Origin blog.csdn.net/weixin_53344209/article/details/130488817