目录
1.1 一切初始化皆可用“ { } ”,无论是内置类型还是自定义类型。
C++11简介
都说十年磨一剑,C+11像脱胎于C+98的一种新语言。C++11能更好地用于系统开发和库开发、语法更加泛化和简单化、更加稳定和安全。我们来学习下现实中较为实用的语法。
1.统一的列表初始化
1.1一切初始化皆可用“ { } ”,无论是内置类型还是自定义类型。
内置类型:
int a[] = {1,2,3,4};
double d[3] = {1.1 ,2.2,3.3};
自定义类型:
struct A
{
int a;
int b;
} ;
A a = {1,2};
还可以用于new表达式中
int *pa = new int[4]{0};
至于map 等一些容器也可以
map<int, string> mp = { {1,"a"},{2,"b"},{3,"c"}};
vector<int> v ={2,34,54,5,6};
1.2initializer_list
关于这些容器用花括号来调用构造函数初始化,是怎么实现的?C++增加了一个类型,叫initializer_list。常量的花括号列表被认定为initializer_list类型。
我们可以用迭代器来访问。
讲了这么多,我们去看看这些容器为什么依靠 initializer_list支持花括号初始化的。
C++11中新增了这样一个构造函数,用来支持initializer_list。用花括号初始化时,会调用参数含有initializer_list的构造函数。
当然C++11也增加了operator = 的重载来支持initializer_list。
2.声明
c++11提供了多种简化声明的方式。
2.1auto
在C++11之前,auto是一个存储类型的说明符,表明变量是局部自动存储类型。我曾经遇到过一道题,题中问到函数形参的存储类型是什么?函数的隐含的存储类型是什么?答案是:函数形参的存储类型是auto,函数的隐含的存储类型是extern。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。当然我觉得它和范围for匹配起来很便捷,或者要定义一个迭代器时,冗长的类型很烦,可以用auto。
2.2decltype
关键字decltype将变量的类型声明为表达式指定的类型。
3.范围for循环
其实,用起来简单了,但范围for的底层还是用迭代器实现的。
4.智能指针
void fuc()
{
int* a1 = new int[10];
//...
//抛异常
delete a1;
}
int main()
{
try
{
fuc();
}
catch (...)
{
;
}
}
如果fuc()函数中,在delete之前有函数抛出异常,那么根据异常捕获机制,则会跳到main函数中,进行catch,则fuc()函数中抛异常以后的语句都不会执行。那么在堆上申请的空间就不会释放,导致内存泄漏。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
所以提出了,这样一种思想。RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。衍生出智能指针的概念,就是将申请的资源交给一个对象,通过对象管理资源,在对象生命周期结束时,利用析构函数去释放资源。
但有个问题,怎么拷贝呢?是浅拷贝还是深拷贝呢?
两个智能指针去管理同一块资源,若是用系统默认的浅拷贝,到这俩智能指针的生命周期结束时,同时调用析构函数,但是是去析构同一片空间,导致:
那我们用深拷贝呢?那就更不行了,我们的目的是两个智能指针管理同一片空间,如果深拷贝会导致两个智能指针管理两片资源,与起初理念相悖。
4.1auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。我们看看这个指针解决问题了吗?
成功了诶!但是它解决了两个智能指针管理同一片空间的问题了吗?
我们看看:
第一步 一切还好
一旦当完成拷贝构造之后,有东西变遭了
诶?我第一个智能指针怎么被置为空了,说好一起管理一片空间的,但是变为第二个智能指针管理这片资源。这叫做管理权转移,被拷贝对象悬空。(说好一起到白头,他却偷偷焗了油)
为什么呢?其实底层的拷贝构造函数就是这样实现的。
这样的实现会被吐槽好多年,但于事无补,auto_ptr已经被钉在了耻辱柱上。
我们主要来学习unique_ptr和share_ptr。
4.2unique_ptr
简单粗暴,从源头上解决问题。就是不让你拷贝。
实际上还是没解决
4.3shared_ptr
真正解决拷贝问题的还是shared_ptr,其中用到了引用计数的方法。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
每有一个智能指针指向同一片资源,这片资源的计数加一,在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
话不多说,我们来实现下一些关键成员函数:
void Erase()
{
if (--(*_pCount) == 0 && _ptr)
{
delete _ptr;
_ptr = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
shared_ptr(T* ptr)
:_ptr(ptr)
,_pCount(new int(1))
{
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
(*_pCount)++;
}
~shared_ptr()
{
Erase();
}
shared_ptr<T>& operator = (const shared_ptr<T> & sp)
{
if (this->_ptr != sp._ptr)
{
Erase();
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
return *this.
}
4.4weak_ptr
再讲weak_ptr之前,先抛出个问题。
这是因为智能指针赋值不了给原生指针。我们把结构改一下,把原生指针改掉。
这下可以链接了,但是又出个问题,我们运行一下,看会不会调用析构函数。
为什么没调用析构函数呢?这下导致了内存泄漏。因为:
按构造函数相反的顺序来调析构函数。先调p1的析构函数,p1这时计数不为1,--之后不为0所以析构不了,它要等另一个和它管理同一片资源的智能指针析构,那个智能指针就是p2的prev,p2的prev要等p2的析构函数,所以p1释放不了。轮到调p2的析构函数了,p2这时计数不为1,--之后不为0所以析构不了,它要等另一个和它管理同一片资源的智能指针析构,那个智能指针就是p1的next,p1的next要等p1的析构函数,但是p1的析构函数已经调过了,导致p1 和 p2 永远释放不了。
是时候到weak_ptr出现了,他来了,扶大厦之将倾 挽狂澜于既倒。
可以这样修改:
weak_ptr他不参与管理,游离于世外,他管理的资源,计数不会加加。而且它支持用shared_ptr来构造。
结果:
来实现下,彻底搞懂
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())//获得shared_ptr的_ptr
{}
weak_ptr<T>& operator = (const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
_ptr = sp.get()
}
}
实际上,weak_ptr出现的原因就是解决shared_ptr的循环计数的问题,如果遇到shared_ptr互相指向的问题,可以来使用weak_ptr。注意,他只能通过shared_ptr对象来构造。weak_ptr并没有重载operator->和operator *操作符,也不可以直接通过weak_ptr使用对象。
4.5释放对应的资源
在写这些智能指针的时候,都是无脑的去delete,这时因为是new出来的一个变量,可以去delete。那以下得情况呢?
unique_ptr<int> up2(new int[10]);
unique_ptr<date> up3((date*)malloc(sizeof(date)* 10));
unique_ptr<file> up4((file*)fopen("test.cpp", "r"));
创建对象与对应的释放资源就根本对不上,这时要上我们的仿函数了。也要改一下我们写的智能指针的模板。
template<class T>
struct default_delete//默认的仿函数
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T, class D = default_delete<T>>
class unique_ptr
{
public:
...
在析构函数中,用仿函数创建对象,用对象调用()的重载函数去释放空间。
~unique_ptr()
{
if (_ptr)
{
D del;
del(_ptr);
_ptr = nullptr;
}
}
下面的仿函数就解决了示例一。
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout <<"delete[]"<<ptr << endl;
delete[] ptr;
}
};
...
这样一个一个去传仿函数,便解决了对应的问题。
未完待续...