This article is not suitable for beginners

目录

make function

allocate_shared

make_unique &&make_shared

Advantage

limit

Parameter function of custom deleter

Pimpl

SFINAE mechanism

explain

introducing

viod_t (C++ 17)


make function

There 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_shared

Except 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:

// allocate_shared example
#include <iostream>
#include <memory>int main () {std::allocator<int> alloc;    // the default allocator for intstd::default_delete<int> del; // the default deleter for intstd::shared_ptr<int> foo = std::allocate_shared<int> (alloc,10);auto bar = std::allocate_shared<int> (alloc,20);auto baz = std::allocate_shared<std::pair<int,int>> (alloc,30,40);std::cout << "*foo: " << *foo << '\n';std::cout << "*bar: " << *bar << '\n';std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';return 0;
}

result:

*foo: 10
*bar: 20
*baz: 30 40

make_unique &&make_shared

Advantage

We 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:

std::shared_ptr<Widget> s(new Widget);//不使用make函数
std::unique_ptr<Widget> u(new Widget);//不使用make函数

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:

void processWidget(std::shared_ptr<Widget> spw,int priority);

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 :

processWidget(std::shared_ptr<Widget>(new Widget),createMyInt())

Why, because before processWidget runs, it will produce the following three parts of code:

  1. new Widget
  2. cerateMyInt ()
  3. shared_ptr constructor

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.

processWidget(std::make_shared<Widget>(),createMyInt())

The third reason is to improve efficiency , consider the following code:

std::shared_ptr<Widget> spw(new Widget);

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
the performance advantages of std::make_shared can also be extended to the std::allocate_shared function.

limit

However, 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.

auto spv = std::make_shared<std::vector<int>>{1,2,3};
auto spv = std::make_shared<std::vector<int>>({1,2,3});

But if we insist on using initializer_list, we can use this compromise:

//使用std::initializer_list创建
auto initList = { 10, 20 };
//使用std::initializer_list为参数的构造函数来创建std::vector
auto spv = std::make_shared<std::vector<int>>(initList);

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:

new (std::nothrow)

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 deleter

Going back to the previous memory leak problem, if we want to pass a custom deleter, then we will have to call this form:

void myLog(T* t) //deleter
{cout << typeid(t).name() << endl;
}
auto myDel = [](Ispliter* s)
{myLog(s);delete s;
};
processWidget(std::shared_ptr<T>(new T,myDel),creatMyInt())//as before,可能会造成内存泄露

A very simple solution is as follows:

std::shared_ptr<T>s(new T,myDel)
processWidget(s,creatMyInt())

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.

processWidget(std::move(s),creatMyInt())

Pimpl

Another 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:

#include<iostream> //*.h file
class Widget {
public:Widget(); //declaration only
private:struct Impl;std::unique_ptr<Impl> pImpl;//use smart pointer
};
#include "test.h" //in "*.cpp"
#include <string>
#include <vector>struct Widget::Impl {std::string name;std::vector<double> data;
};
Widget::Widget():pImpl(std::make_unique<Impl>())
{} 

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.

Widget w; //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:

#include<iostream> //*.h file
class Widget {
public:Widget(); //declaration only~Widget();
private:struct Impl;std::unique_ptr<Impl> pImpl;//use smart pointer
};
#include "test.h" //in "*.cpp"
#include <string>
#include <vector>struct Widget::Impl {std::string name;std::vector<double> data;
};
Widget::Widget():pImpl(std::make_unique<Impl>())
{}
Widget::~Widget(){};

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:

#include<iostream>//*.h file
class Widget {
public:Widget();~Widget(); //declaration onlyWidget(Widget&& rhs) = default; Widget& operator=(Widget&& rhs) = default;
private:struct Impl;std::unique_ptr<Impl> pImpl;//use smart pointer//instead of raw pointer
};

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.

#include<iostream>//*.h file
class Widget {
public:Widget();~Widget(); //declaration onlyWidget(Widget&& rhs)noexcept; Widget& operator=(Widget&& rhs)noexcept;
private:struct Impl;std::unique_ptr<Impl> pImpl;
};
#include "test.h" // "*.cpp"
#include <string>
#include <vector>struct Widget::Impl {std::string name; //as beforestd::vector<double> data;
};
Widget::Widget():pImpl(std::make_unique<Impl>())
{} //std::unique_ptr
Widget::~Widget() {}; //~Widget definition
Widget::Widget(Widget&& rhs)noexcept =default;
Widget& Widget::operator=(Widget&& rhs)noexcept=default;

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:

#include<iostream>//*.h file
class Widget {
public:Widget();~Widget(); //declaration onlyWidget(Widget&& rhs)noexcept; Widget& operator=(Widget&& rhs)noexcept;Widget(const Widget& w);Widget& operator=(const Widget& w);
private:struct Impl;std::unique_ptr<Impl> pImpl;//use smart pointer//instead of raw pointer
};
#include "test.h" // "*.cpp"
#include <string>
#include <vector>struct Widget::Impl {std::string name; //as beforestd::vector<double> data;
};
Widget::Widget():pImpl(std::make_unique<Impl>())
{} //std::unique_ptr
//via std::make_unique
Widget::~Widget() {}; //~Widget definition
Widget::Widget(Widget&& rhs)noexcept =default;
Widget& Widget::operator=(Widget&& rhs)noexcept=default;Widget::Widget(const Widget& w) :pImpl(std::make_unique<Impl>(*w.pImpl))
{}Widget& Widget::operator=(const Widget & w)
{*pImpl = *w.pImpl;return *this;
}

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 mechanism

The 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.

explain

Explicitly 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:

template<typename T>
typename T::value_type sum(T a, T b, typename T::value_type c)
{c = *a + *b;return c;
}
void main()
{vector<int> v{ 1,2,3,4 };cout << sum(begin(v), end(v)-1, 0) << endl;
}

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.

void main()
{std::vector<int> v{ 1,2,3,4 };cout << sum(begin(v), end(v)-1, 0) << endl;cout << sum(v.data(), v.data() + 3, 0) << endl;//error
}

To fix this, we had to write one more:

template<typename T>
T sum(T* a, T* b, T c)
{c= *a + *breturn c;
} 

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.

introducing

Next, 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.

template<typename T> struct add_ref { using type = T&; };
template<typename T>
using add_ref_t = typename add_ref<T>::type;static_assert(std::is_same< add_ref_t< int >, int& >::value, "ops");
static_assert(std::is_same< add_ref_t< int&& >, int& >::value, "ops");
static_assert(std::is_same< add_ref_t< int&>, int& >::value, "ops");

We may take it for granted that this is true for all types, such as changing int to long, double, but except for one:

static_assert(!std::is_same< add_ref_t<void>, void >::value, "ops");

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:

template<> struct add_ref<void> { using type = void; };
template<> struct add_ref<const void> { using type = void; };
template<> struct add_ref<volatile void> { using type = void; };
template<> struct add_ref<const volatile void> { using type = void; };

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:

template<class T> struct remove_ref { using type = T; };
template<class T> struct remove_ref<T&> { using type = T; };
template<class T> struct remove_ref<T&&> { using type = T; };
template<class T> using remove_ref_t = typename remove_ref<T>::type;template<class T, class Enable> struct ALR { using type = T; }; //base template
template<class T> struct ALR<T,remove_ref_t<T&>> { using type = T&; }; //specialization
template<class T> struct add_l_ref:ALR<T, remove_ref_t<T>> {}; //point to use
template<class T> using add_l_ref_t = typename add_l_ref<T>::type;

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:

static_assert(std::is_same<add_l_ref_t<int>, int&>::value, "ops");//special
static_assert(std::is_same<add_l_ref_t<void>, void>::value, "ops");//base
static_assert(std::is_same<add_l_ref_t<int&&>, int&>::value, "ops");//special

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.

template<class...> using void_t = void;

If your compiler does not support c17, the code above is equal to the code following, which might be a little easier to understand.

template<class...T> struct temp {using type =void};
template<class...T> using void_t = typename temp<T...>::type;

with void_t, we can implement the above function with the following code:

template<class T, class Enable> struct ALR { using type = T; };// base
template<class T> struct ALR<T,void_t<T&>> { using type = T&; };//special
template<class T> struct add_l_ref:ALR<T, void> {};//point to use

This metafunction is used in template metaprogramming to detect ill-formed types in SFINAE context:

// primary template handles types that have no nested ::type member:
template< class, class = void >
struct has_type_member : std::false_type { };// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };

Let us write some code to test them:

struct test
{using type = int;
};int main()
{cout << has_type_member<test>() << endl;//sepcialcout<< has_type_member<int>() << endl;//base
}

the output is 1 and 0 separately;

It can also be used to detect the validity of an expression:

// primary template handles types that do not support pre-increment:
template< class, class = void >
struct has_pre_increment_member : std::false_type { };
// specialization recognizes types that do support pre-increment:
template< class T >
struct has_pre_increment_member<T,std::void_t<decltype( ++std::declval<T&>() )>> : std::true_type { };

let us test it:

struct test1
{int i;test1& operator++() {++this->i;return *this;}
};
struct test2
{int i;
};
int main()
{cout << has_pre_increment_member<test2>() << endl;cout << has_pre_increment_member<test1>() << endl;
}

the output is 0 and 1 separately;

小结:

第一次尝试用纯英文写,感觉挺痛苦的。

下一节写if constexpr(c++17)以及concept(c++20)。如果有其他篇幅加上CRTP以及memory order

C++ advanced(4)make function and SFINAE相关推荐

  1. 深入理解JavaScript内部原理(5): function

    本文是翻译http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/#introduction 概要 In this article we w ...

  2. Shader编程学习笔记(五)—— Fixed Function Shader 1

    Fixed Function Shader 在学习固定管线着色器中要涉及到的知识点是: Properties Material Lighting SetTexture Pass 首先来回忆一下Shad ...

  3. 强化学习笔记(三)Value Function Approximation

    目录 学习考察 引言 1.值函数近似(VFA) 2.什么是oracle? 如果没有oracle,我们如何优化价值拟合函数呢? 3.蒙特卡洛值函数近似(MC-VFA) 4.时序差分函数近似(TD-VFA ...

  4. 《A Graduate Course in Applied Cryptography》Chapter 11 Public key encryption (2)ss-trapdoor function

    原文教材 与 参考资料: Boneh Dan , Shoup Victor . A Graduate Course in Applied Cryptography[J]. 该书项目地址(可以免费获取) ...

  5. MySQL之Procedure(存储过程)和Function(函数)

    一组预先编译好的SQL语句的集合,相当于批处理语句 存储过程 特点:提高代码的重用性.简化操作.减少编译次数和数据库的连接次数,提高了效率 ①创建 CREATE PROCEDURE 存储过程(参数列表 ...

  6. 深度学习笔记(四) cost function来源和证明

    1)什么是代价函数 WIKI的解释: Cost function In economics, the cost curve, expressing production costs in terms ...

  7. V-REP教程(七)API function

    浏览Regular API function list (by category) 1.文件操作类的 simSaveScene("a.ttt") 2.General object ...

  8. (七)Builtin Function

    内置函数在解释器中存放在builtins 哈希表中 var builtins = map[string]*object.Builtin{"len": &object.Bui ...

  9. 机器学习笔记(十)——Logistic Function AND Softmax Function

    一.说明 在逻辑回归和一些机器学习算法中, Logistic函数和Softmax函数是常用到的,今天就先讨论下这两个函数. 二.Logistic Function Logistic function一 ...

  10. navicat存储过程返回值为空_Excel VBA解读(128):Function过程详解——枯燥的语法...

    学习Excel技术,关注微信公众号: excelperfect 在<Excel VBA解读(27):看看VBA的Sub过程和Function过程>中,我们讲解了Function过程的基本形 ...

最新文章

  1. js 排列 组合 的一个简单例子
  2. 使用canvas绘制动画时钟
  3. 【数字逻辑设计】推气泡
  4. mongodb连接认证失败
  5. 轻量服务器怎么换系统,轻量服务器更换系统
  6. zabbix自动发现redis端口并监控redis性能
  7. Bugku-Web-xxx二手交易市场
  8. 线程同步:喂,SHE
  9. 常见的c语言头文件作用,C语言的头文件的作用是什么?
  10. 《计算机基础知识REVIEW》の操作系统---存储管理
  11. python之路_保留原搜索页面条件
  12. perl语言学习 教程
  13. ssm生鲜超市管理系统的设计与实现毕业设计源码261635
  14. 注册亚马逊网站云服务器,免费午餐:亚马逊免费云主机注册使用全攻略
  15. Android 手表WearOs 禁止滑动返回、监听滑动事件分发
  16. android 排他button,javascript排他思想
  17. 【编程题】【Scratch二级】2019.06 飞不出去的蝴蝶
  18. 隐马尔可夫模型(Baum Welch算法与Viterbi算法)
  19. Zoom and pan, introduction to FabricJS part 5(缩放和平移,介绍Fabric.js第五部分)
  20. Ps cs6/cs5安装提示错误(37)的解决方法

热门文章

  1. matlab ols regress,计量经济学简单线性回归OLS的Matlab程序.pdf
  2. Windows下运行Makefile
  3. html圣诞效果,HTML5实现圣诞树效果
  4. 信息搜集-读取微信聊天记录
  5. java自行车DH32,中国国际自行车嘉年华之Enduro、DH装备篇
  6. 香橙派基于翔云的人脸识别
  7. XCOM2中敌对生物设计分析(ADVENT篇)
  8. iOS系统语音播报文字
  9. web服务器基于那个协议,网页浏览服务基于什么协议 Web服务器是基于什么协议...
  10. 达人篇:3.1.3)FAI 首件检验