12.1、动态内存与智能指针
静态内存:用于保存局部static对象、类static数据成员以及定义在任何函数之外的变量。(static对象在使用之前分配,程序结束销毁。)
栈内存:用来保存定义在函数内的非static对象。(栈对象,仅在程序块运行时存在)
分配在静态或栈内存中的对象由编译器自动创建和销毁。
智能指针:shared_ptr(允许多个指针指向同一个对象),unique_ptr(独占所指向的对象)
weak_ptr伴随类:弱引用,指向shared_ptr所管理的对象。
12.1.1、shared_ptr类
每个shared_pt都有一个关联的计数器,称为引用计数。当我们拷贝(初始化、参数传递、函数返回值)一个shared_ptr时,计数器递增。当被赋予新值或被销毁,计数器递减。
析构函数:一般用于释放对象所分配的资源。
程序使用动态内存的三个原因:程序不知道自己需要使用多少个对象、程序不知道所需对象的准确类型、程序需要在多个对象间共享数据。
StrBlob类
#ifndef SRC_STRBLOB_H_
#define SRC_STRBLOB_H_
#include<vector>
#include<string>
#include<initializer_list>
#include<memory>
#include<stdexcept>
using namespace std;
class StrBlob{
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
//添加和删除元素
void push_back(const string &t) { data->push_back(t); }
void pop_back();
//元素访问
string& front();
const string& front() const;
string& back();
const string& back() const;
private:
shared_ptr<std::vector<std::string>> data;
//如果data[i]不合法,抛出一个异常
void check(size_type i, const std::string &msg) const;
};
//两个构造函数都使用初始化列表来初始化其data成员
StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) { }
void StrBlob::check(size_type i, const string &msg) const
{
if(i >= data->size())
throw out_of_range(msg);
}
string& StrBlob::front()
{
//如果vector为空,check会抛出一个异常
check(0, "front on empty StrBlob");
return data->front();
}
//const版本的front
const string& StrBlob::front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back()
{
check(0,"back on empty StrBlob");
return data->back();
}
//const版本back
const string& StrBlob::back() const
{
check(0,"back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0,"pop_back on empty StrBlob");
data->pop_back();
}
#endif /* SRC_STRBLOB_H_ */
测试程序
#include<iostream>
#include "StrBlob.h"
using namespace std;
int main(int argc, char **argv)
{
StrBlob b1;
{
StrBlob b2 = { "a", "an", "the"};
b1 = b2;
b2.push_back("about");
cout << b2.size() << endl; //4
}
cout << b1.size() << endl; //4
cout << b1.front() << " " << b1.back() << endl; //a about
const StrBlob b3 = b1;
cout << b3.front() << " " << b3.back() << endl; //a about
return 0;
}
12.1.2、直接管理内存
使用new和delete直接管理内存
#include<iostream>
#include<vector>
using namespace std;
//shared_ptr<vector<int>> new_vector(void) //智能指针管理内存,在主函数末尾不需要主动释放内存
vector<int> *new_vector() //返回动态分配int的vector
{
//return make_shared<vector<int>> ();
return new (nothrow) vector<int>; //如果分配失败,new返回一个空指针
}
void read_ints(vector<int> *pv) //读取标准输入
{
int v;
while(cin >> v)
pv->push_back(v);
}
void print_ints(vector<int> *pv) //打印
{
for(const auto &v : *pv)
cout << v << " ";
cout << endl;
}
int main(int argc, char *argv[])
{
vector<int> *pv = new_vector();
if(!pv){
cout << "内存不足!" << endl;
return -1;
}
read_ints(pv);
print_ints(pv);
delete pv; //释放内存
pv = nullptr; //将指针重置
return 0;
}
12.1.3、shared_ptr和new结合使用
接受指针参数的智能指针构造函数是explicit的,因此,不能将一个内置指针隐式转换为一个智能指针,必须直接初始化形式。
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。
不要混合使用普通指针和智能指针,使用一个内置指针来访问一个智能指针所负责的对象是很危险的。
智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。
不要使用get初始化另一个智能指针或为智能指针赋值。
12.1.4、智能指针和异常
使用智能指针可以确保在异常发生会,资源被正确的释放。
如果使用内置指针管理内存,且在new之后在对应的delete之前发生了异常,则内存不会被释放。
#include<iostream>
#include<memory>
using namespace std;
struct destination{};
struct connection{};
connection connect(destination *pd)
{
cout << "打开链接" << endl;
return connection();
}
void disconnect(connection c)
{
cout << "关闭链接" << endl;
}
//未使用shared_ptr的版本
void f(destination &d)
{
cout << "直接管理connect" << endl;
connection c = connect(&d);
//忘记调用disconnect关闭链接
cout << endl;
}
void end_connection(connection *p) {disconnect(*p);}
//使用shared_ptr的版本
void f1(destination &d)
{
cout << "用shared_ptr管理connect" << endl;
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
//shared_ptr<connection> p(&c, [](connection *p) { disconnection(*p); });
//忘记调用disconnect关闭链接
cout << endl;
}
int main(int argc, char *argv[])
{
destination d;
f(d);
f1(d);
return 0;
}
12.1.5、unique_ptr
一个unique_ptr“拥有”它所指向的对象。必须采用直接初始化的形式。不支持普通的拷贝和赋值操作。
可以拷贝或赋值一个将要销毁的unique_ptr。
12.1.6、weak_ptr
12.2、动态数组
12.2.1、new和数组
//调用get_size确定分配多少个int
int *pia = new int[get_size()]; //pia指向第一个int
分配一个数组会得到一个元素类型的指针
delete [] pia; //释放动态数组
//up指向一个包含10个未初始化的int数组
unique_ptr<int[]> up(new int[10]);
up.release(); //自动用delete[]销毁其指针
shared_ptr不直接支持管理动态数组,必须提供自己定义的删除器。不支持下标运算,不支持指针的算术运算,使用get获取一个内置指针。
12.2.2、allocator类
将内存分配和对象构造分离开来。它分配的内存是原始的、未构造的。
拷贝和填充未初始化内存的算法
#include<iostream>
#include<string>
#include<memory>
using namespace std;
int main(int argc, char *argv[])
{
allocator<string> alloc;
//分配100个未初始化的string
auto const p = alloc.allocate(4);
string s;
string *q = p; //q指向第一个string
while(cin >> s && q != p + 4)
alloc.construct(q++, s); //用s初始化string
const size_t size = q - p; //记住读取了多少个string
//使用数组
for(size_t i = 0; i < size; ++i)
cout << p[i] << " " << endl;
while(q != p) //使用完毕后释放已构造的string
alloc.destroy(--q);
alloc.deallocate(p, 100); //释放内存
return 0;
}