回顾复习一些常见的C++问题,以及一些没有仔细考虑过的地方。(任何问题欢迎邮件:admin@franciswu.top)
公司还是VS2005搞破鞋,导致新特性都没咋用过,都是零散看了一些,最近刚好有个讲座,下面是学习笔记以及思考。
1、nullptr
比较老的版本里一般使用#define NULL 0
来判断空指针,在一些情况下,比如函数参数是指针的,函数又有重载(int),函数匹配会有问题,造成一些不优雅的情况。新版本添加了定义std::nullptr
来专门指代空指针来解决这个问题。
2、自动类型推导 auto/decltype
auto定义:
- 对于变量,指定其类型将从其初始化器自动推导而出
- 对于函数,指定其返回类型是尾随的返回类型或将从其return 语句推导出 (C++14 起)
- 对于非类型模板形参,指定其类型将从参数推导出(C++17 起)
正常使用姿势:
std::vector<int>V;
/*老版本*/
for(std::vector<int>::iterator iter = V.begin();
iter != V.end(); ++iter)
{
...
}
/*新版本*/
for(auto iter = V.begin(); iter != V.begin(); ++iter)
{
...
}
/*最简单版本 基于auto关键字的和范围for循环*/
for(auto val : V)
{
...
}
特殊例子:
/* auto 常用于无名类型,例如 lambda 表达式的类型*/
auto lambda = [](int x) { return x + 3; };
auto ret = lambda(10); // ret的类型为int
auto lambda = [](auto a, auto b) { return a * b; };// c++14
decltype关键字用来取变量的类型,可以取到引用,右值、lambda这样的特殊类型。
int i = 33;
decltype(i) j = i * 2; // j的类型和i的类型相同,都为int
auto f = [](int a, int b) -> int
{
return a * b;
};
decltype(f) g = f; // lambda 的类型是独有且无名的
3、初始化器列表 std::initializer_list
std::initializer_list是一个模板类,提供可以像初始化数组一样初始化对象,包括STL容器。
例子:
/*老版写法*/
int iarray[] = {1,2,3,4,5}
std::vector<int>vec(iarray, iarray+sizeof(iarray)/sizeof(int));
/*或者*/
int* begin = iarray;
int* end = iarray+sizeof(iarray)/sizeof(int);
for(int* iter = begin; iter != end; ++ iter)
{
vec.push_back(*iter);
}
/*新写法*/
std::vector<int>vec0 = {1,2,3,4,5};
std::unordered_map<int,std::string> map0 =
{
{0,"str0"},
{1,"str1"},
{2,"str2"},
{3,"str3"},
};
一个initializer_list当出现在以下两种情况的被自动构造:
- 当初始化的时候使用大括号进行初始化,包括函数调用时和赋值。
- 当涉及到for(initializer : list), list被自动构造成initializer_list对象,即initializer_list对象只能使用大括号初始化,拷贝一个initializer_list对象并不会拷贝里面的元素,只是引用,而且元素都是const的。
/*自己实现支持initializer_list初始化*/
template<typename T>
class initializer_example
{
public:
typedef T value_type;
initializer_example(std::initializer_list<value_type> _Ilist)
{
for (auto& obj: _Ilist)
{
obj_pool.push_back(obj);
}
/*or*/
/*obj_pool = _Ilist;*/
}
protected:
std::vector<value_type> obj_pool;
};
initializer_example<int> ie = {1,2,3,4};
4、基于范围的循环
例子:
int iarr[] = { 0,1,2,3,4,5 };
/* c++ 11 前*/
for (size_t i = 0; i < sizeof(iarr)/sizeof(iarr[0]); i++)
{
printf("%d", iarr[i]);
}
/*基于范围的for循环*/
for (auto val : iarr)
{
printf("%d", val);
}
/*初始化器列表基于范围的for循环*/
for (auto val : {0,1,2,3,4,5})
{
printf("%d", val);
}
最后这个例子大括号使initializer_list被自动构造了。
从c++17开始支持同时取索引和值,类似于python的循环取值。
例子:
#python 2.7
vec = [1,2,3,4,5,6]
#k是索引 v为值
for k,v in enumerate(vec):
print str(k) +','+str(v)
dic = {1:'one',2:'two'}
for k,v in dic.items():
print str(k) +','+str(v)
C++:
std::unordered_map<int, std::string> map0 = {
{ 0, "value0"},
{ 1, "value1" },
{ 2, "value2" },
{ 3, "value3" },
};
for (auto [k,v] : map0){ } // since c++ 17
int *p = new int[6];
/* 编译失败,原因是指针p没有begin函数,*/
/* 而且指针是没有长度信息的。 因此基于范围的for循环不起作用*/
for (auto i : p)
{
printf("%d", i);
}
5、override/final标识符
override:
-
在成员函数声明或定义中, override 确保该函数为虚并覆写来自基类的虚函数,若此非真则程序为病式(生成编译错误)。
-
override 是在成员函数声明器后使用时拥有特殊含义的标识符,其他情况下它不是保留的关键词。
override的作用简单来说就是显示表明申明的函数是用来覆写基类的虚函数的,防止手误写错了参数造成没有覆写等等一些情况。
final:
- 在虚函数声明或定义中使用时, final 确保函数为虚且不可被派生类覆写。若这么做则程序生成编译时错误。在类定义中使用时,确保类不能被继承。
/*函数定义中使用*/
class A
{
public:
virtual void fun() { printf("A::fun\n"); }
virtual void fun1() { printf("A::fun1\n"); }
};
class B : public A
{
public:
virtual void fun()override { printf("B::fun\n"); }
virtual void fun1()override final { printf("B::fun1\n"); }
};
class C : public B
{
public:
virtual void fun()override { printf("C::fun\n"); }
virtual void fun1()override { printf("C::fun1\n"); } // 编译错误
};
/*类定义中使用 */
class A
{
public:
virtual void fun() { printf("A::fun\n"); }
virtual void fun1() { printf("A::fun1\n"); }
};
class B final : public A
{
public:
virtual void fun()override { printf("B::fun\n"); }
virtual void fun1()override { printf("B::fun1\n"); }
};
class C : public B /* 编译错误,B不能用作基类 */
{
public:
virtual void fun()override { printf("C::fun\n"); }
virtual void fun1()override { printf("C::fun1\n"); }
};
6、default/delete特性
C++的类有四类特殊成员函数,分别是:默认构造函数、默认析构函数、拷贝构造函数、拷贝赋值操作符、转移构造函数、转移赋值操作符。这些类的特殊成员函数负责创建、初始化、销毁,或者拷贝或移动类的对象。
如果我们没有显式地为一个类定义某个特殊成员函数,有需要使用该特殊成员函数时,编译器会隐式地为这个类生成一个默认的特殊成员函数。同时C++规定,一旦程序员实现了这些函数的自定义版本,则编译器不会再生成默认版本。注意:只是不再自动生成默认版本,可以手动生成默认版本。
如果我们自定义了非默认的构造函数,下面的代码将编译错误:
class A
{
public:
A(int) {}
virtual~A(){ }
};
A a;// class A 没有默认构造函数
这时候我们需要手动实现默认构造函数:
class A
{
public:
A() {}
A(int) {}
virtual~A(){ }
};
A a;
缺点:手动实现的默认构造函数执行效率没有编译器为我们实现的高。
C++11 标准引入了一个新特性:defaulted 函数。只需在函数声明后加上“=default;”就可将该函数声明为 defaulted 函数,编译器将为显式声明的 defaulted 函数自动生成函数体。
class A
{
public:
A() = default;
A(int) {}
virtual~A() { }
};
A a;
/* 限制:*/
/* Defaulted 函数特性仅适用于类的特殊成员函数,*/
/* 且该特殊成员函数没有默认参数。*/
/* Defaulted 函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义。*/
class A{
public:
A() = default; //Inline defaulted 默认构造函数
A(const A&);
A& operator = (const A&);
~A() = default; //Inline defaulted 析构函数
};
A::A(const A&) = default; //Out-of-line defaulted 拷贝构造函数
A& A::operator = (const A&) = default; //Out-of-line defaulted 拷贝赋值操作符
delete 特性:当我们没有显示地实现一个特殊的成员函数时,又不想编译器为我们实现默认的特殊成员函数,我们可以使用delete关键字来告诉编译器。
class A
{
public:
A() = default;
A& operator = (const A&) = delete;
A(int) {}
virtual~A() { }
void foo() {}
};
A a1;
A a2 = a1; // 正确,调用编译器隐式生成的默认拷贝构造函数
A a3;
a3 = a1; // 错误,不能引用A::operator = (const A&),这是个已删除的函数
7、右值引用和move(转移)语义
C++中所有的表达式和变量肯定是左值或者右值。左值就是非临时对象,可以多条语句中使用的对象。右值就是临时对象,只在当前语句中有效。
int i = 0;
这条语句中,i就是一个左值,0是右值。在C++11之前,右值是不能被引用的。
右值引用(Rvalue Reference)是C++11中引入的新特性,它实现了转移语义(Move Sementics)和完美转发(Perfect Forwarding)。主要效果:
- 消除两个对象交互时不必要的对象拷贝,节省资源,提高效率
- 简洁明确地定义泛型函数
左值的声明符号是“&”:void foo(int& i){}
右值的声明符号是“&&”:void foo(int&& i){}
void print_value(int& i) {
std::cout << "LValue is: " << i << std::endl;
}
void print_value(int&& i) {
std::cout << "RValue is: " << i << std::endl;
}
void forward_value(int&& i) {
print_value(i);
print_value(std::forward<int&&>(i));
print_value(std::forward<int&>(i));
print_value(std::forward<int>(i));
}
int main() {
int a = 0;
print_value(a);
print_value(1);
forward_value(2);
}
/* LValue is: 0 */
/* RValue is: 1 */
/* LValue is: 2 */
/* RValue is: 2 */
/* LValue is: 2 */
/* RValue is: 2 */
从运行结果可以看出,虽然forward_value接收时是右值,但是到了第一个print_value接收时变成了左值,第二个print_value时,我们使用了C++11的的std::forward进行完美转发,保持了参数右值的特性,第三print_value虽然也使用了完美转发,但是去除了右值特性,最后一个print_value完美转发为右值。
完美转发std::forward:将一组参数原封不动地传给另一个函数,也就是说参数的左值/右值和const/non-const属性不会发生变化。
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{ /* forward an lvalue as either an lvalue or an rvalue */
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ /* forward an rvalue as an rvalue */
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
std::forward可以保存参数的左值或右值特性。其有两个重载
- 将左值转发为左值或者右值,并保持属性不变
- 转发右值为右值并禁止右值的转发为左值
转移语义
右值引用是用来支持转移语义的,临时对象的维护(创建和销毁)对性能有严重影响,转移语义可以将资源(堆、系统对象等)从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,大幅度提高C++应用程序的性能。
在现有的C++机制中,我们可以定义拷贝构造函数和拷贝赋值运算符,要实现转移语义需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制拷贝构造函数和赋值操作符会被调用。
std::move:
C++11标准库提供的获取右值引用的方法,我们可以将一个左值转为右值引用。
int i = 0;
int&& j = std::move(i);
void foo(int&& in){}
foo(std::move(i));
class ExampleForMove
{
public:
ExampleForMove() {
printf("call default constructor.\n");
}
ExampleForMove(const ExampleForMove& other) {
printf("call copy constructor.\n");
}
ExampleForMove(ExampleForMove&& other) {
printf("call move constructor.\n");
}
ExampleForMove& operator=(const ExampleForMove& other) {
printf("call copy operator.\n");
return *this;
}
ExampleForMove& operator=(ExampleForMove&& other) {
printf("call move operator.\n");
return *this;
}
};
ExampleForMove e0;
ExampleForMove e1(e0);
ExampleForMove e2(std::move(ExampleForMove()));
ExampleForMove e3 = std::move(ExampleForMove());
ExampleForMove e4;
e4 = e0;
ExampleForMove e5;
e5 = ExampleForMove();
call default constructor.
call copy constructor.
call default constructor.
call move constructor.
call default constructor.
call move constructor.
call default constructor.
call copy operator.
call default constructor.
call default constructor.
call move operator.
如果没有实现转移构造函数和转移操作符的话,会调用默认的构造函数和拷贝操作符。
7.1 RVO技术(return value optimization,匿名返回值优化)
转移语义在上面的例子中其实出现了一些难以理解的地方,在没有任何优化的情况下,返回一个对象的话,会构造一个中间值,然后再拷贝或者赋值操作。
class Point3d
{
public:
int m_x;
int m_y;
int m_z;
public:
Point3d(int x,int y,int z):m_x(x),m_y(y),m_z(z)
{
cout << "constructor"<<endl;
}
~Point3d()
{
cout << "deconstructor"<<endl;
}
Point3d(const Ponint3d &other)
{
this.m_x = other.m_x;
this.m_y = other.m_y;
this.m_z = other.m_z;
cout << "copy constructor"<<endl;
}
Point3d &operator=(const Point3d &other)
{
if(this != &other)
{
this.m_x = other.m_x;
this.m_y = other.m_y;
this.m_z = other.m_z;
}
cout << "operator="<<endl;
return *this;
}
}
Point3d factory()
{
Point3d po(1,2,3)
return po;
}
int main()
{
Point3d p = factory();
return 1;
}
在没有任何优化的情况下 打印情况如下:
constructor //factory函数中构造po对象
copy constructor //factory函数中用po对象拷贝构造临时对象_temp
deconstructor //factory函数中返回时,析构掉局部对象po
copy constructor //main函数中用factory函数中拷贝构造的临时对象拷贝构造对象p
deconstructor //析构临时对象_temp
deconstructor //main函数结束时,析构对象p
VS在debug模式下使用RVO优化,在《在深度探索C++对象模型》书中,对应的解释为,编译器将factor函数改写为了如下的伪代码形式:
void factory(const Point3d &_result)
{
Point3d po;
po.Point3d::Point3d(1,2,3);
_result.Point3d::Point3d(po); /* 用po拷贝构造_result; */
po.Point3d::~Point3d(); /* po对象析构 */
return;
}
int main()
{
Point3d p;
factory(p);
return 0;
}
constructor
copy constructor
deconstructor
deconstructor //main函数中p的析构
7.2 NRVO技术(Named Return Value Optimization,具名返回值优化)
编译器将factory()函数改写为如下形式:
Point3d factory()
{
return Point3d(1,2,3);
}
同时RVO和NRVO:
void factory(const Point3d &_result)
{
_result.Point3d::Point3d(1,2,3); /* 直接将p作为参数构造 */
return;
}
/*
constructor
deconstructor
*/
TODO:看到这里其实会发现,在支持了移动语义的情况下,函数返回具体对象的时候,如果这个对象明确可以优化,编译器会使用RVO&NRVO,不会使用移动语义。(给我的感觉是这样,并不是很确定)
附一个有移动语义的string实现:
class MyString
{
private:
char* _data;
size_t _len;
void _init_data(const char *s)
{
_data = new char[_len+1];
memcpy(_data, s, _len);
_data[_len] = '\0';
}
public:
MyString()
{
_data = NULL;
_len = 0;
}
MyString(const char* p)
{
_len = strlen (p);
_init_data(p);
}
MyString(const MyString& str)
{
_len = str._len;
_init_data(str._data);
std::cout << "Copy Constructor is called! source: " << str._data << std::endl; }
MyString& operator=(const MyString& str)
{
if (this != &str)
{
_len = str._len;
_init_data(str._data);
}
std::cout << "Copy Assignment is called! source: " << str._data << std::endl;
return *this;
}
MyString(MyString&& str)
{
std::cout << "Move Constructor is called! source: " << str._data << std::endl;
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL;
}
MyString& operator=(MyString&& str)
{
std::cout << "Move Assignment is called! source: " << str._data << std::endl;
if (this != &str)
{
clear();
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL;
}
return *this;
}
void clear()
{
delete _data;
_data = NULL;
_len = 0;
}
virtual ~MyString()
{
if (_data) free(_data);
}
};
8、Lambda expressions
完整格式:
[ captures ] <tparams>( params ) specifiers exception attr -> ret{ body }
常见用法:
class MyClass
{
public:
MyClass() {}
~MyClass() {}
void Func(int i)
{
[](){}; /* OK: 空函数 */
[&] {}; /* OK :默认以引用捕获 */
[&, i] {}; /* OK :以引用捕获,除了 i 以值捕获 */
[&, &i] {}; /* 错误:默认以引用时的以引用捕获 */
[&, this] {}; /* OK :等价于 [&] */
[&, this, i] {}; /* OK :等价于 [&, i] */
[=] {}; /* OK :默认以复制捕获 */
[=, &i] {}; /* OK :以复制捕获,除了 i 以引用捕获 */
[=, *this]{}; /* C++17 前:错误:非法语法 */
[=, this] {}; /* C++20 前:错误: = 为默认时的 this */
/* C++20 起: OK :同 [=] */
}
int mem_i;
};
9、可变模板参数
C++11的新特性–可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
template <class ...Args>
void foo(Args... args) {}
args 参数前面有省略号,所以它就是一个可变模板参数,称之为“参数包”,里面包含了0到N个模板参数。参数包无法直接获取每个参数,一般只能通过展开参数包。
通过递归方式展开参数包demo:
template<typename T>
T adder(T v) {
return v;
}
template<typename T, typename... Args>
T adder(T first, Args... args) {
return first + adder(args...);
}
long sum = adder(1, 2, 3, 4, 5);
std::string s1 = "a", s2 = "d", s3 = "d", s4 = "e", s5 = "r";
std::string str = adder(s1 + s2 + s3 + s4 + s5);
10、智能指针
智能指针原理是将指针作为对象来管理,这样对象自己完成对内存的管理,避免一些情况下内存泄露。(比如一些异常返回时,忘记释放内存)
10.1 shared_ptr
shared_ptr
多个指针指向相同对象,使用引用计数,每一个shared_ptr
的拷贝都指向相同的内存,每次使用,内存引用计数+1,析构一次引用计数-1,引用计数减为0时,自动删除所指向的堆内存。
shared_ptr
内部的引用计数是线程安全的,但是对象读取需要加锁。
- 智能指针本身是一个模板类,传入指针通过构造函数初始化或者用
make_shared
函数初始化。不能直接将指针直接赋值给智能指针,一个是类一个是指针(错误示范:std::shared_ptr<int> p4 = new int(1);
正确:std::shared_ptr<int> p4(new int(1));
)。 get
函数获取原始指针- 不能使用一个原始指针初始化多个
shared_ptr
,否则会造成内存二次释放 - 避免循环引用,会导致内存无法释放
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main(void)
{
/* two shared pointers representing two persons by their name*/
shared_ptr<string> pNico(new string("nico"));
shared_ptr<string> pJutta(new string("jutta"),
/* deleter (a lambda function) */
[](string *p)
{
cout << "delete " << *p << endl;
delete p;
}
);
/* capitalize person names */
(*pNico)[0] = 'N'; /*pNico的string变为"Nico" */
pJutta->replace(0, 1, "J"); /* pJutta的string变为"Jutta" */
/* put them multiple times in a container */
vector<shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
/* print all elements */
for (auto ptr : whoMadeCoffee)
cout << *ptr << " ";
cout << endl;
/* overwrite a name again */
*pNico = "Nicolai";
/* print all elements */
for (auto ptr : whoMadeCoffee)
cout << *ptr << " ";
cout << endl;
/* print some internal data */
cout << "use_count: " << whoMadeCoffee[0].use_count() << endl;
system("pause");
return 0;
}
/* Jutta Jutta Nico Jutta Nico */
/* Jutta Nicolai Jutta Jutta Nicolai */
/* use_count: 4 //初始化1个 数组中3个 */
10.2 unique_ptr
unique_ptr
“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr
指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr
指针本身的生命周期:从unique_ptr
指针创建时开始,直到离开作用域。
#include <iostream>
#include <memory>
int main() {
{
std::unique_ptr<int> uptr(new int(10)); /* 绑定动态对象 */
/* std::unique_ptr<int> uptr2 = uptr; //不能赋值 */
/* std::unique_ptr<int> uptr2(uptr); //不能拷贝 */
std::unique_ptr<int> uptr2 = std::move(uptr); /* 转换所有权 */
uptr2.release(); /* 释放所有权 */
}
/* 超过uptr的作用域,內存释放 按照上面代码utpr本身已经为空了 */
}
10.3 weak_ptr
weak_ptr
是为了配合shared_ptr
而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*
和->
,它的最大作用在于协助shared_ptr
工作,像旁观者那样观测资源的使用情况。weak_ptr
可以从一个shared_ptr
或者另一个weak_ptr
对象构造,获得资源的观测权但weak_ptr
没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr
的成员函数use_count()
可以观测资源的引用计数,另一个成员函数expired()
的功能等价于use_count()==0
,但更快,表示被观测的资源(也就是shared_ptr
的管理的资源)已经不复存在。
weak_ptr
可以使用一个非常重要的成员函数lock()
从被观测的shared_ptr
获得一个可用的shared_ptr
对象,从而操作资源。但当expired()==true
的时候,lock()
函数将返回一个存储空指针的shared_ptr
。
注意:lock()
实际上是创建了一个新的shared_ptr
。
#include <iostream>
#include <memory>
int main() {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()){
std::shared_ptr<int> sh_ptr2 = wp.lock(); /* get another shared_ptr */
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
/* delete memory */
}
/* 1 1 2 */
10.4 shared_ptr 循环引用的产生
示例:
#include <iostream>
#include <memory>
using namespace std;
struct Node
{
shared_ptr<Node> _pre;
shared_ptr<Node> _Next;
~Node()
{
cout << "~Node" << this << endl;
}
int data;
};
void test()
{
shared_ptr<Node> Node1(new Node);
shared_ptr<Node> Node2(new Node);
Node1->_Next = Node2;
Node2->_pre = Node1;
cout << "Node1.use_count():" << Node1.use_count() << endl;
cout << "Node2.use_count():" << Node2.use_count() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
/* 2 2 */
看这个例子比较直接,所谓的智能指针是把指针作为一个对象之后,他们之间的赋值关系进行了控制。两个智能指针之间赋值,如果是shared
就不会进行拷贝,两个对象的有效数据是一个,然后根据计数来维护这个数据。其他的同理。
这时候为了避免上面这个情况,需要使用weak_ptr
来防止循环引用,修改:
#include <iostream>
#include <memory>
using namespace std;
struct Node
{
weak_ptr<Node> _pre;
weak_ptr<Node> _Next;
~Node()
{
cout << "~Node" << this << endl;
}
int data;
};
void test()
{
shared_ptr<Node> Node1(new Node);
shared_ptr<Node> Node2(new Node);
Node1->_Next = Node2;
Node2->_pre = Node1;
cout << "Node1.use_count():" << Node1.use_count() << endl;
cout << "Node2.use_count():" << Node2.use_count() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
/* 1 1 */
11、std::function
类模板 std::function
是通用多态函数封装器。这个不是很明白应用场景。具体参考(https://zh.cppreference.com/w/cpp/utility/functional/function)
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
/* 存储自由函数 */
std::function<void(int)> f_display = print_num;
f_display(-9);
/* 存储 lambda */
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
/* 存储到 std::bind 调用的结果 */
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
/* 存储到成员函数的调用 */
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
/* 存储到数据成员访问器的调用 */
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
/* 存储到成员函数及对象的调用 */
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
/* 存储到成员函数和对象指针的调用 */
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
/* 存储到函数对象的调用 */
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
输出:
-9
42
31337
314160
314160
num_: 314159
314161
314162
18