目录
12.1 动态内存与智能指针
- 静态内存:保存局部static对象、类static数据成员和定义在任何函数之外的变量
- 栈内存:保存定义在函数内的非static对象
- 内存池:被称为自由空间或堆,程序用堆来存储动态内存分配的对象,动态对象的生存期由程序控制
- new,在动态内存中为对象分配空间并返回指向该对象的指针
- delete,接受一个动态对象的指针并销毁该对象,释放与之关联的内存
- shared_ptr允许多个指针指向同一对象,unique_ptr则独占所指向的对象,weak_ptr:伴随类,是一种弱引用,指向shared_ptr所管理的对象。三种类型都定义在memory头文件中。
12.1.1 shard_ptr类
shared_ptr<string> p1; // shared_ptr,可以指向string
shared_ptr<list<int>> p2; // shared_ptr,可以指向int的list
if (p1 && p1->empty()) {
*p1 = "Hi"; //如果p1指向一个空string,解引用p1,浆新值赋予string
}
- shared_ptr和unique_ptr都支持的操作
- shared_ptr独有的操作
1. make_shared函数
- 头文件 #include<memory>
shared_ptr<string> sp2 = make_shared<string>(); //初始化智能指针
shared_ptr<string> sp3 = make_shared<string>("b");//初始化智能指针
auto sp4 = make_shared<vector<string>>(); //sp4指向一个动态分配的空vector<string>
- 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论我们拷贝一个share_ptr,计数器都会递增。当我们给一个shared_ptr赋值或者shared被销毁,计数器就会递减。当用一个shared_ptr初始化另外一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值(赋值给其他的),计数器都会递增。一旦一个share_ptr的计数器变为0,它就会释放自己所管理的对象。
- sp1 = sp2; sp2计数器值增加,右值的计数器增加,左值指向的对象的计数器减少,减少为0时自动释放对象。
auto r = make_shared<int>(53); // r指向的int只有一个引用者
r = q; // 递增q指向的对象的引用计数
// 递减r指向的对象的引用计数
// r原来指向的对象的计数值为0,自动释放内存
- 当指向对象的最后一个shared_ptr 被销毁时,shared_ptr 类会自动销毁此对象。它是通过特殊的成员函数析构函数来控制对象销毁时做什么操作。对于一块内存,shared_ptr 类保证只要有任何shared_ptr 对象引用它,它就不会被释放。
- 如果我们忘记了销毁程序不再需要的shared_ptr,程序仍然会正确运行,但会浪费内存。如果你将shared_ptr存放于一个容器中,而后不在需要全部元素,而只使用其中的一部分,要记得调用erase删除不再需要的那些元素。
- 程序使用动态内存的三种原因:
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据。
- 我们希望定义一个Blob类,保存一组元素,与容器不同,我们希望Blob对象的不同拷贝之间共享相同的元素。既当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。
Blob<string> b1; // 空Blob
{
Blob<string> b2 = {"a", "an", "the"};
b1 = b2; // b1和b2共享相同的元素
} // b2被销毁了,但b2中的元素不能被销毁
// b1指向最初由b2创建的对象
- 定义一个管理string的类,命名为StrBlob。
#include <iostream>
#include <string>
#include <memory> // 智能指针和动态分配内存
#include <vector>
#include <initializer_list> // 初始值列表
#include <stdexcept>
using namespace std;
class StrBlob
{
public:
typedef svector<string>::size_type size_type; // 使用类型别名
StrBlob();
StrBlob(initializer_list<string> il);
size_type size() const { return data->size(); }
bool empty() { return data->empty(); }
//添加删除元素
void push_back(const std::string &s){ data->push_back(s); }
void pop_back();
//访问元素
string& front();
string& back();
const string& front()const;
const string& back() const;
private:
shared_ptr<vector<string>> data; // 使用智能指针指向数组
//private 检查函数。
void check(size_type i, const string &msg)const;
};
//默认构造函数
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, string &msg)const
{
if(i >= data->size())
throw out_of_range(msg);
}
const string& StrBlob::back()const
{
check(0, "back on empty StrBlob");
return data->back();
}
//避免代码重复和编译时间问题,用non-const版本调用const版本
//在函数中必须先调用const版本,然后去除const特性
//在调用const版本时,必须将this指针转换为const,注意转换的是this指针,所以<>里面是const StrBlob* 是const的类的指针。
//调用const版本时对象是const,所以this指针也是const,通过转换this指针才能调用const版本,否则调用的是non-const版本,non-const调用non-const会引起无限递归。
//return时,const_cast抛出去除const特性。
string& StrBlob::back()
{
const auto &s = static_cast<const StrBlob*>(this)->back(); //auto前面要加const,因为auto推倒不出来const。
return const_cast<string&>(s);
}
const string& StrBlob::front()const
{
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::front()
{
const auto &s = static_cast<const StrBlob*>(this)->front();
return const_cast<string&>(s);
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
int main()
{
shared_ptr<StrBlob> sp;
StrBlob s({"wang","wei","hao"});
StrBlob s2(s);//共享s内的数据
string st = "asd";
s2.push_back(st);
//s2.front();
std::cout << s2.front() << std::endl;
std::cout << s2.back() << std::endl;
}
// 可以输出s和s2的size( )是相等的证明他们共享的是同一块内存。
12.1.2 直接管理内存
- 使用new动态分配内存和初始化对象
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
//在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回该指向该对象的一个指针。
int *p = new int;
string *ps = new string; // 初始化为空string
int *pi = new int; //pi指向一个未初始化的对象
int *p2 = new int(10);
string *p3 = new string(10,'a');
vector<string> *p4 = new vector<string>{"a","b","c"};
//也可以对动态分配的对象进行值初始化,只需在类型名之后加一对空括号
string *p5 = new string(); //值初始化
string *p6 = new string; //默认初始化
int *p7 = new int; //但是对于内置类型是未定义的。*p7值未定义
//对动态分配的对象进行初始化通常是个好主意。
int *p8 = new int(); //值初始化为0
//auto
auto p8 = new auto("abc");
auto p9 = new auto{1,2,3}; //error ??
//const
//一个动态分配的const对象必须进行初始化。
const string *p10 = new const string("aha");
//内存耗尽
int *p11 = new int; //如果分配失败,new会抛出一个std::bad_alloc
int *p12 = new (nothrow) int; //如果分配失败返回一个空指针。 bad_alloc和nothrow定义在#include <new>
}
- delete p;释放p所指向的对象的那块内存区域,释放后p仍然指向那块区域(测试时输出的地址仍然相同),但是释放后输出的对象已无效。一般我们可以在释放delete p后, p = nullptr。这样明确指针不指向其他的区域。
int i, *pi1 = &i, *pi2 = nullptr;
delete i; // 错误:i不是一个指针
delete *pi1; // 错误:pi1不是一个动态分配的指针
delete *pi2; // 正确:pi2是一个空指针
const int *pci = new const int(1024);
delete pci; // 正确
int *p = new int (42);
auto q = p;
delete p;
p = nullptr; //这里对于q依旧没有效果,只能提供有限的保护
- 空悬指针:指向一块曾经保存数据现在已经无效的内存的指针。
12.1.3 shard_ptr和new结合使用
- 可以用new来初始化智能指针
- 接受指针参数的智能指针构造函数是explicit(避免隐式转换)的,我们不能将一个内置指针隐式转换为一个智能指针。
shared_ptr<int> p2(new int(1024)); // 正确:使用了直接初始化形式
shared_ptr<int> p1 = new int(1024); // 错误:不接受隐式初始化
shared_ptr<int> clone(int p){
return new int(p); // 错误:必须进行隐式转换
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); // 正确
}
- 默认情况下,一个用来初始化智能指针的普通指针必须指向一块动态分配的内存,因为智能指针默认使用delete释放它所关联的对象。如果将智能指针绑定到其他类型的指针上,我们必须自己定义自己的释放操作。
- 当将一个shared_ptr 绑定到一个普通指针时,我们就将内存管理交给了shared_ptr,之后我们就不应该使用内置指针来访问shared_ptr指向的内存了。使用内置指针来访问智能指针所附则的对象是非常危险的,我们不知道对象何时被销毁。
int *x = new int(1024); // 危险:x是一个普通指针,不是一个智能指针
process(x); // 错误:不能将int*转换成智能指针
process(shared_ptr<int>(x)); // 合法,但是内存会被释放
int j = *x; // 错误:内存被释放
- 永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。普通指针不能自动转化为智能指针。使用get返回的指针不能使用delete。
{
auto p = make_shared<int>(20);
auto q = p.get();
delete q; // delete q释放一次内存,离开作用域再次释放内存,一共释放了两次
}
12.1.4 智能指针和异常
- 使用智能指针,即使程序块过早结束,智能指针类也能确保内存不再需要时将其释放。但是普通指针就不会。
void fun( )
{
int *p = new int(42);
//如果这时抛出一个异常且未捕获,内存不会被释放,但是智能指针就可以释放。
delete p;
}
- 标准很多都定义了析构函数,负责清理对象使用的资源,但是一些同时满足c和c++的设计的类,通常都要求我们自己来释放资源。通过智能指针可以很好的解决这个问题。
struct destination; // 表示我们正在连接什么
struct connection; // 使用连接所需的信息
connection connect(destination*); // 打开连接
void disconnect(connect); // 关闭给定的连接
void f(destination &d)
{
connection c = connect(&d);
disconnect(d);//如果没有调用disconnect,那么永远不会断开连接。
}
// 优化
void end_disconnect(connection*p) {disconnect(p);}
void f(destination &d)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_disconnect);
// 使用连接
// 当f退出时(即使是由于异常退出),connection会被正确关闭
}
- 智能指针的陷阱
- 不使用相同的内置指针值初始化(或reset)多个智能指针:多个智能指针还是单独的指向内置指针的内存,use_count分别为1
- 不delete get( )返回的指针
- 不使用get( )初始化或reset另一个智能指针
- 如果你使用get( )返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变得无效了
- 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器(删除函数向上面的disconnect( ))
- shared_ptr 的传递删除器(deleter)方式比较简单, 只需要在参数中添加具体的删除器函数名, 即可; 注意是单参数函数;
- unique_ptr 的删除器是函数模板(function template), 所以需要在模板类型传递删除器的类型(即函数指针(function pointer)), 再在参数中添加具体删除器;
12.1.5 unique_ptr
- 一个unique_ptr 拥有它所指向的对象,和shared_ptr不同,某个时刻只能有一个unique_ptr 指向一个给定对象,当unique_ptr 被销毁时,对象也被销毁
- unique没有类似make_shared,必须手动new,将其绑定。由于unique_ptr独占它所指向的对象,因此他不支持普通的拷贝和赋值。但是有种特殊的拷贝可以支持:我们可以拷贝或赋值一个即将要被销毁的unique_ptr。
- 允许返回一个局部的拷贝对象或者从函数返回unique_ptr。
#include <memory>
#include <iostream>
#include <string>
using namespace std;
unique_ptr<int> clone(int p)
{
unique_ptr<int>q(new int(p));
return q;
//return unique<int>q(new int(p)); // 两种情况都满足条件
}
int main()
{
unique_ptr<string>p(new string("aaa"));
shared_ptr<string>p2(new string("aaa"));
//unique_ptr<string>p3(p); error:不能拷贝
//unique_ptr<string>p4 = p; error:不能赋值
unique_ptr<string>p5;
string s = "a";
//p5.reset(&s); error:两次释放
//两种转移所有权的方法
unique_ptr<string>p6(p.release());//p.release(),释放p对指针对象的控制权,返回指针并将p置空,并不会释放内存
unique_ptr<string>p7;
p7.reset(p6.release()); //p6释放控制权,p7指向这个对象。
// 如果不用另一个智能指针来保存release返回的指针,则需要负责资源释放
p2.release(); // 错误:p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); // 正确:但必须记得手动释放
delete p;
//特殊的拷贝和赋值
int i = 10;
clone(i);
}
- 类似shared_ptr, unique_ptr 默认情况下用delete释放它指向的对象,和shared_ptr 一样我们可以重载一个unique_ptr 中默认的删除器类型。重载一个unique_ptr 中的删除器会影响到unique_ptr 类型及如何构造该类型的对象,我们必须在尖括号中unique_ptr 指向类型之后提供删除器的类型,在创建或reset一个这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器)。
#include <memory>
#include <iostream>
using namespace std;
typedef int connection;
connection* connect(connection *d)
{
cout << "正在连接..." << endl;
d = new connection(40);
return d;
}
void disconnect(connection *p)
{
cout << "断开连接..." << endl;
}
int main()
{
connection *p,*p2;
p2 = connect(p);
cout << p << endl;
cout << *p2 << endl;
unique_ptr<connection, decltype(disconnect)*> q(p2,disconnect);
//在尖括号中提供类型,圆括号内提供尖括号中的类型的对象。
//使用decltype()关键字返回一个函数类型,所以必须添加一个*号来指出我们使用的是一个指针
}
12.1.6 weak_ptr
- weak_ptr 是一种不控制对象生存期的智能指针,它指向由一个shared_ptr 管理的对象。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //弱指针共享p,但p的引用计数没有改变
- 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock检查weak_ptr指向的对象是否存在。
if (shared_ptr<int> np = wp.lock()){ // 如果np不为空则条件成立
// 在if中np与p共享对象(利用np访问共享对象)
}
#include <memory>
#include <iostream>
using namespace std;
int main()
{
shared_ptr<int>p0 = make_shared<int>(42);
auto p1 = p0;
cout << "p1 count:" << p1.use_count() << endl;
weak_ptr<int>p2;
weak_ptr<int>p3(p1); //与p1指向相同的对象
p3 = p0; //与p0指向相同的对象
p2 = p3;
cout << "p2 count:" << p2.use_count() << endl;
//w.reset() 将w置空
p2.reset(); //将p2置空
cout << "p2 count:" << p2.use_count() << endl;
cout << "p3 count:" << p3.use_count() << endl;
//w.expired() 若w.use_count()为0,返回true,否则返回false expired(过期的)
//w.expried() 也就是判断shared_ptr的count为0吗。
cout << "p2 expired:" << p2.expired() << endl;
cout << "p3 expired:" << p3.expired() << endl;
//w.lock() 如果expired为true,返回一个空的shared_ptr,否则返回一个指向w的对象的shared_ptr
//w.lock() 也就是如果shared_ptr的count为0,返回空,不为0,返回shared_ptr。
auto p4 = p2.lock();
cout << "p4 count:" << p4.use_count() << endl;
auto p5 = p3.lock();//引用计数加1
cout << "p5 count:" << p5.use_count() << endl;
}
- std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。
- std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。此外,std::weak_ptr 还可以用来避免 std::shared_ptr 的循环引用。
- 定义一个StrBlobPtr(内部weak_ptr)类打印StrBlob中的元素,上面的StrBlobPtr 例子的改进。StrBlobPtr内部是weak_ptr实现的,它实际上就是一个助手类,作用就是类似一个旁观者,一直观测StrBlob的资源使用情况。
- 通过使用weak_ptr,不会影响一个给定的StrBlob所指向的vector的生存期,但是可以阻止用户访问一个不再存在的vector的请求。
/*
*避免拷贝,多个指针共用一个vector<string>
*使用weak_ptr访问共享的对象
* */
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <fstream>
#include <sstream>
using namespace std;
class StrBlob;
class StrBlobPtr;
class StrBlob
{
public:
friend class StrBlobPtr;
typedef vector<string>::size_type size_type;
StrBlob(); //默认构造函数
StrBlob(initializer_list<string> il); //拷贝构造函数
size_type size() const { return data->size(); } //对data进行解引用就是对vector<string>操作
string& front();
string& back();
const string& front()const;
const string& back()const;
void push_back(const string &s) { data->push_back(s); }
void pop_back();
//StrBlobPtr begin() { return StrBlobPtr(*this); }
//StrBlobPtr end() { auto ret = StrBlobPtr(*this, data->size());
// return ret; }
private:
void check(size_type sz, std::string msg)const;
std::shared_ptr<std::vector<std::string>>data;
};
string& StrBlob::front()
{
const auto &s = static_cast<const StrBlob*>(this)->front();
return const_cast<std::string&>(s);
}
string& StrBlob::back()
{
const auto &s = static_cast<const StrBlob*>(this)->back();
return const_cast<std::string&>(s);
}
const string& StrBlob::front()const
{
check(0, "front on empty vector");
return data->front();
}
const string& StrBlob::back()const
{
check(0, "back on empty vector");
return data->back();
}
void StrBlob::check(size_type sz, std::string msg)const
{
if(sz >= data->size())
throw std::out_of_range(msg);
}
StrBlob::StrBlob():
data(std::make_shared<std::vector<std::string>>()) { }
StrBlob::StrBlob(std::initializer_list<std::string> il):
data(std::make_shared<std::vector<std::string>>(il)) { }
/* --------------------------------------------------------------------------------- */
//必须定义在StrBlobPtr的后面
//否则error: invalid use of incomplete type ‘class StrBlob’
class StrBlobPtr
{
public:
friend StrBlob; // 声明为友元类
StrBlobPtr():curr(0){ }
StrBlobPtr(StrBlob &s, size_t sz = 0): // 注意这里不能将StrBlobPtr绑定到一个const StrBlob对象
wptr(s.data), curr(sz){ } // 用智能指针初始化并设置好下标
std::string& deref()const;//返回当前string
StrBlobPtr& incr(); //递增
private:
shared_ptr<vector<string>> check(size_t i, const string &msg)const;
weak_ptr<vector<string>> wptr;
size_t curr; //当前下标
};
StrBlobPtr& StrBlobPtr::incr()
{
check(curr, "increment past end of StrBlobPtr");
++curr; //推进当前位置。
return *this; //为什么要return *this, 如果再次自加可以重复,举个例子就像赋值一样 a = b = c; 如果不返回对象不能继续赋值。
} //return *this是一份拷贝。 return this是地址。
string& StrBlobPtr::deref()const
{
auto p = check(curr, "dereference past end"); //shared_ptr引用计数会增加,但是作用域结束后,引用计数又会减1
return (*p)[curr]; //p是所指的vector
}
//check检查是否存在shared_ptr和大小
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg)const
{
auto ret = wptr.lock(); //检查是否存在,存在返回shared_ptr,不存在返回空的shared_ptr.
if(!ret)
throw std::runtime_error("unbound StrBlobPtr");
if(i >= ret->size())
throw std::out_of_range(msg);
return ret;
}
int main(int argc, char*argv[])
{
std::fstream is(argv[1]);
std::string s;
StrBlob S;
while(std::getline(is, s))
{
std::string temp;
std::istringstream ist(s);
while(!ist.eof())
{
ist >> temp;
S.push_back(temp);
}
}
std::cout << "size:" << S.size() << std::endl;
StrBlobPtr sp(S);
for(auto i = 0; i < S.size(); ++i)
{
std::cout << sp.deref() << std::endl;
sp.incr();
}
}
12.2 动态数组
- new和delete一次只能分配和释放一个对象,但有时我们需要一次为很多对象分配内存的功能,C++和标准库引入了两种方法,另一种new 和 allocator。
- 使用allocator 通常会提供更好的性能和更灵活的内存管理能力。
- 大多数应用应该使用标准库容器而不是动态分配的数组,使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。
- 直到学习完第13章,不要在类内的代码中分配动态内存
12.2.1 new和数组
- 动态数组不是数组类型。不能用范围for循环处理动态数组中的元素
int *pia = new int[get_size()]; // 方括号中的量不必是常量
typedef int arrT[42]; // arrT表示42个int的数组类型
int *p = new arrT; // 分配一个42个int的数组,p指向第一个int
#include <iostream>
#include <memory>
using namespace std;
typedef int arr[10];
int main()
{
int *p = new int[10];
int *p2 = new arr;
for(int i = 0; i < 10; i++)
{
p[i] = i;
}
for(int i = 0; i < 10; i++)
cout << p[i] << " ";
cout << endl;
//for(const int i : p); //error:动态分配数组返回的不是数组类型,而是数组元素的指针。所以不能用范围for
/*---------------------------------- */
//初始化动态数组
int *pi = new int[10]; //未初始化
int *pi2 = new int[10](); //初始化为0,且有括号必须为空
string *ps = new string[10]; //10个空string
string *ps2 = new string[10](); //10个空string
//可以使用列表初始化
int *pi3 = new int[10]{1,2,3,4,5,6,7,8,9,0};//初始值列表里的值不能多于容量,否则new失败,不会分配内存
string *ps3 = new string[10]{"a","b","c","d","e","f","g","h","i",string(3,'x')};
//释放动态数组
delete []pi3; //必须要加[]括号,且释放动态数字时是逆序释放。如果delete动态数组不加[],行为是未定义的。
/*----------------------------------- */
//智能指针和动态数组,标准库定义了特别的unique_ptr来管理,当uo销毁它管理的指针时,会自动调用delete [];
int *p5 = new int[10];
//unique_ptr<int[]> up;
unique_ptr<int[]> up(p5);
for(int i = 0; i < 10; ++i)
cout << up[i] << " ";
cout << endl;
//如果使用shared_ptr的话我们必须自己定义delete函数
shared_ptr<int>sp(new int[10], [](int *p) { delete []p;});
//智能指针不支持算数类型,如果要访问数组中的元素,必须使用get函数返回一个内置指针。
cout << (*sp.get()) << endl;
}
- 如果在delete一个数组指针时忘记加方括号,或者在delete一个单一对象时使用了方括号,编译器可能不会给出警告。
- 可以使用unique_ptr管理new分配的数组
unique_ptr<int[]> up(new int[10]);
unique.release(); // 自动用delete[]销毁其指针(跟int型指针不一样?)
for(size_t i = 0; i != 10; ++i){
up[i] = i; // 允许使用下标访问
}
4. 使用shared_ptr不能直接管理动态数组,必须自定义删除器
shared_ptr<int> sp(new int[10], [](int *p){delete []p;});
sp.reset(); // 使用lambda释放数组
// 注意没有下标运算
12.2.2 allocator类
- 引入allocator的原因是new类上的缺陷,new它将内存分配和对象构造结合到了一起,可能会造成两次赋值,更重要的是,那些没有默认构造函数的类就不能使用动态分配数组了。
- allocator类定义在头文件memory中,将内存分配和对象初始化分开。
#include <iostream>
#include <memory>
#include <string>
using namespace std;
int main()
{
//allocator<T> a; 定义了一个名为a的allocator对象,可以为T对象分配空间
allocator<string> alloc;
//a.allocate(n); 为T分配n个空间
string *const p = alloc.allocate(10); //为10个string分配了内存,且内存是原始的,未构造的
cout << sizeof(p) << endl;
//释放p中地址的内存,这快内存保存了n个T对象,p必须是allocte返回的指针,n必须是allocate(n)的n
//且在调用deallocate时必须先毁坏这块内存中创建的对象
alloc.deallocate(p,10);
//p是allocate返回的指针,construction是传递给类型T的构造函数,在p指向的内存中构造一个对象
//alloc.construct(p, construction)
//对p指向的对象进行析构函数。
//alloc.destroy(p);
allocator<string>alloc2;
auto const p2 = alloc2.allocate(10);
auto q = p2;
auto q2 = p2;
//为了使用我们allocate的内存,必须用construct构造对象,使用未定义的内存,其行为是未定义的。
alloc2.construct(q++, "sssss");
cout << *q2++ << endl;
alloc2.construct(q++, "he");
cout << *q2++ << endl;
alloc2.construct(q++, 10, 'x');
cout << *q2 << endl;
//对使用过的内存进行释放,调用string的析构函数,注意不能destory未使用的内存。
while(q2 != p2)
alloc2.destroy(q2--);
//元素被销毁后,我们可以重新使用这块内存,也可以归还给系统
alloc2.deallocate(p2, 10);
//deallocate的指针不能为空,必须指向allocate分配的内存,且deallocate和allocate的大小相同。
}
- 标准库还为allocator定义了两个伴随算法,在未初始化的内存中创建对象,都定义在头文件memory。
uninitialized_copy(b,e,b2) b,2是输入容器的迭代器,b2是内存的起始地址,要保证空间足够
uninitialized_copy_n(b,n,b2) b是输入容器的起始迭代器,复制n个,复制到以b2为起始地址的动态内存中
uninitialized_fill(b,e,t) b,e是动态内存的起始和终止位置,t是要fill的元素
uninitialized_fill_n(b,n,t) b是动态内存的起始,fill n个,t是要fill的元素