| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++ advanced(4)make function and SFINAE -> 正文阅读 |
|
[C++知识库]C++ advanced(4)make function and SFINAE |
?This article is not suitable for beginners 目录 Parameter function of custom deleter make functionThere are three make functions in C++. The most commonly used one is make_shared. In addition, there are make_unique and allocate_shared proposed by C++14.? allocate_sharedExcept that the first parameter is an allocator object used to dynamically allocate memory, it behaves like std::make_shared. Of course, there are some differences that make_shared supports array types (C++17), but allocate_shared does not support, this function is in boost implemented in the library. This function is very simple, put a simple example:
result:
make_unique &&make_shared??AdvantageWe know that to construct smart pointers, it is generally recommended to use direct new or make, but we still hope to use make as much as possible. Because if you use new it will be like this:
In this way, we need to write the widget twice, and code duplication should be avoided. Duplication in the source code will increase the number of compilations, so it is not recommended. The second reason is that there may be memory leaks when the function is passed. For example, we define an interface as follows:
There seem?to be some problems with passing?value, but since it is a copy of std::shared_ptr, it is acceptable, but if we pass in an rvalue, it may cause a memory leak :
Why, because before processWidget runs, it will produce the following three parts of code:
?The execution order of these steps cannot be determined. If the above sequence is above, there will be a problem with the execution of the second item, and an exception will be thrown. There is no way to use a smart pointer to take over new, so memory leaks may occur. If you use make instead, you can avoid this problem.
The third reason is to improve efficiency , consider the following code:
Obviously only one new is needed, but everyone knows that the control block inside shared_ptr is actually new, so in fact, new is executed twice, but if you use make function only once, the object and control block are all new at one time. Instead of new in different places twice, the execution speed of the code is accelerated. It is worth noting that if new is separated, some additional record information such as debug information will be allocated, potentially reducing the memory pin number. The efficiency analysis of std::make_shared above is also used for std::allocate_shared, so limitHowever, having said that, make is not used everywhere, so as we all know, shared or unique can customize the deleter, but make cannot customize the deleter . The second limitation comes from the construction method . Normally, a type is created in two ways, with initializer_list or not, depending on whether it is overloaded or not. The make function will perfectly forward its parameters to the object's constructor, but it uses square brackets instead of curly brackets. If we want to initialize the object with curly brackets, that is, initializer_list, we must use new to achieve perfect forwarding. Both of the following methods are wrong.
?But if we insist on using initializer_list, we can use this compromise:
?The above two cases are only for the restrictions of std::unique_ptr, and std::shared_ptr has two other restrictions in addition to the above two restrictions: First of all, we know that in addition to the global operator new, we can customize new in the class. The first parameter must be size_t, and the delete function can be defined at the same time. The first parameter must be void *. This custom method is usually created by new. The size of sizeof(class) comes out of and delete. For shared_ptr, the allocated memory is not only the size of the object, but also needs to add a control block with a size of 16byte, pointing to two reference counts, deleters, and even allocator. Using make is really not a good idea for custom new and delete operations . The fourth problem, since the make function constructs the control block and the object together, if the reference count of the control block is zeroed, the object will be released, but the control block will not be released immediately , because there are two reference counts, namely shared_count And weak_count, as long as these two things are not 0, then never want to release the entire object for a long time. But new will not have this phenomenon, because the memory allocated separately can be reclaimed separately, the control block is reserved and released later, but the object is released first. The fifth question, if new fails,?usually throw?an exception directly. Many people write this:
?In this way, new can avoid the exception of allocating space , but will throw a null pointer. Many developers prefer this form of new for various considerations. If we create smart pointers and want to use this form, then use the constructor function It is inevitable. Parameter function of custom deleterGoing back to the previous memory leak problem, if we want to pass a custom deleter, then we will have to call this form:
A very simple solution is as follows:
But if we use rvalue transfer, it will be much better. Compared with ordinary copy prevention, it also avoids changing the reference count. Since the reference count is of atomic type, the change will be slower.
PimplAnother case is the Pimpl (pointer to implementation) operation, such as the common declaration of a member variable as a pointer to an incomplete type, and then dynamic allocation and recovery in the original data member object. For example this case:
We don't need to define the destructor here, because we use ptr to receive it. The problem is that if we compile it like this, it will report an error.
The reason is that it thinks I'm using the undefined type Impl, but the truth is we defined it, why is this happening. Let's look at the code, here we use sizeof for our type Impl, which is generated when the default destructor is generated. ?The key is that when the widget's destructor is generated, it is necessary to ensure that the definition of Impl is seen by the compiler, so we need to define the destructor after defining the function of Impl to avoid the above error situation:
The reason we declare the destructor here is that we want to complete its definition in another file, which can reduce the workload of compiling with the Widget client. To put it bluntly, it is to isolate changes, which is like a design pattern. And because we declared the destructor, the compiler will deprecate it by default, we need to customize:
But if we call the move construction, there will be the same problem, the compiler thinks that the class Impl we defined has only the declaration and no definition, so we need to write it below the class definition for the same problem.
In actual use, this certainly supports copying, and it is not a simple shallow copy, but due to the existence of move construction and copying, the copy operation is discarded, so we need to write it ourselves:
It is worth noting that if we use shared-ptr instead of unique-ptr, the default copy function will just meet our ideas, just use the default. SFINAE mechanismThe SFINAE mechanism is a very important basis for the composition of the C++ template mechanism and type safety. The full name is Substitution failure is not an error. The general meaning is that as long as the available prototypes (such as function templates, class templates, etc.) are found, there will be no compilation errors. This property is used in generic programming. explainExplicitly specified template parameters are substituted before template parameter inference Deduced arguments and arguments obtained from default values ??will be replaced with deduced arguments for template arguments Let's first look at a few simple examples:
There is no doubt that the output is 5; But if we want to call the following function, we will be warned by the compiler that no matching template parameter can be found.
To fix this, we had to write one more:
This will compile and pass the test. The problem here is that for the first function, the compiler tries to match the first template we wrote, but the matching fails. At this time, no error is reported, but it continues to search for template parameters that can be matched until it finds a template that can match and Until the best match, this is SFINAE, because the compiler often needs to try other possibilities when encountering Failure. introducingNext, let's talk about the complicated SFINAE situation. The purpose of SFINAE is to make the compiler reject code that cannot be compiled and choose the right code for dissimilar input types. For example, the following code is not asked, and there is no problem with static assertion.
We may take it for granted that this is true for all types, such as changing int to long, double, but except for one:
In fact, there is no reference to void, but the existence of void& is acquiesced here, and there will be problems in operation. Then some people may say, well, let us set a partial specialization template for void, indeed, this can solve the problem:
But the problem is, in fact, the compiler can detect the problem, but we need such a partial specialization method for a better match, which is really thankless:?today there is void, tomorrow there will be void2, void3, we can't always be biased Specialize. Is there a better solution? In fact, we can ask the compiler whether our writing is reasonable through SFINAE:
It is easy to understand: When we use the specialization, we are using the?partial specialization part. If the second part in the <> is ill_formed, we can use the base template. In this case, no matter whether the T is well formed or not, we can handle that. then if we run the same code, it will be fine:
viod_t (C++ 17)is there any way to simplify this code,??which means a type expression always produces a simple well-known concrete type? Yes, if we use void_t we can achieve that.
If your compiler does?not support c17, the code above is equal to the code following, which might be a little easier to understand.
with void_t, we can implement the above function with the following code:
This metafunction is used in template metaprogramming to detect ill-formed types in SFINAE context:
Let us write some code to test them:
the output is 1 and 0 separately; ?It can also be used to detect the validity of an expression:
?let us test it:
the output is 0 and 1 separately; 小结:? 第一次尝试用纯英文写,感觉挺痛苦的。 下一节写 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 10:54:56- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |