0
点赞
收藏
分享

微信扫一扫

C++ primer 第十二章 复习

数数扁桃 2022-03-12 阅读 82
c++

C++ primer 第十二章

12.1.1 动态内存与智能指针

全局对象:在程序启动时分配,程序结束时销毁

局部对象:进入其作用域时被创建,离开作用域销毁(栈对象)

静态对象:在第一次使用之前分配,程序结束销毁

动态内存和智能指针

动态内存(堆)的管理是通过一对运算符来完成的

new:在动态内存中为对象分配空间并返回一个指向该对象的指针

delete:接受一个动态内存的指针,销毁该对象,释放相关内存

为了更安全的使用内存,C++11提供了两个智能指针

和普通指针类似,区别在于它负责自动释放所指向的对象

#include<list>
#include<string>
#include<memory>

//智能指针也是模板,默认初始化的智能指针中保存着一个空指针
std::shared_ptr<std::string> p1;//可指向string
std::shared_ptr<std::list<int>> p2; //可指向 指向int的list(即std::list<int>)

//如果p1不为空,检查它是否可以指向一个空字符串
if (p1&&p1->empty()){
	*p1 = "HI";//解引用p1,将HI赋予其指向的对象
}

请添加图片描述

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数

//指向一个值为42的int shared_ptr
std::shared_ptr<int> p3 = std::make_shared<int>(42);
//指向一个值为 "9999999999" 的string
std::shared_ptr<std::string> p4 = std::make_shared<std::string>(10,'9');
//指向一个值初始化的int,即值为0
std::shared_ptr<int> p5 = std::make_shared<int>();

//指向一个动态分配的空std::vector<std::string>
auto p6 = std::make_shared<std::vector<std::string>>();
auto r = std::make_shared<int>(42);//r指向的int只有一个引用者
r = q;//给r赋值,令它指向另一个地址
//递增q的引用计数,递减r原来的引用计数,r原来指向的对象没有引用者,会自动释放
//factory返回一个shared_ptr,指向一个动态分配内存的对象
std::shared_ptr<FOO> factory(T arg){
    return std::shared_ptr<FOO>(arg);
}

void use_factory(T arg){
    std::shared_ptr<FOO> p = factory(arg);
}//离开作用域时, arg对象的内存将被自动释放

使用动态内存的一个常见原因:多个对象共享相同的状态

12.1.2 shared_ptr 和 new 结合使用

直接管理内存

默认初始化

如果定义变量没有被初始化,则变量被赋予默认值

默认值由变量类型决定,且和变量定义位置有关

内置类型,函数体之外被初始化为 0

每个类决定其初始化对象的方式(string 空字符串)

int* pi = new int; //指向一个动态分配的,未初始化的无名对象
string* ps = new string; //初始化空string

直接初始化

string* ps1 = new string; //默认初始化空string
string* ps2 = new string(); //值初始化为空string

int* pi1 = new int; //默认初始化: 其值未定义
int* pi2 = new int(); //值初始化为 0 

使用 auto 从初始化器来推断分配的对象类型

auto p1 = new auto(obj); //p1指向一个与 obj 类型相同的对象,该对象使用 obj 初始化

auto p1 = new auto(a,b,c);  //错误:括号中只能有单个初始化器

用 new 分配 const 对象是合法的

//分配并初始化一个 const int
const int* pci = new const int(1024);
//分配并默认初始化一个 const 的空 string
const string *pcs = new const string;

内存耗尽

int* p1 = new int; //分配失败,则new 抛出std::bad_alloc
int* p1 = new(nothrow); //分配失败,则返回一个空指针
//定位 new 表达式,nothrow 对象告诉它不能抛出异常

指针和 delete

int i, *pil = &i, *pi2 = nullptr;
double *pd = new double(33),*pd2 = pd;

delete i; //错误
delete pil; //错误,非动态内存的指针(没有new也不需要delete)
delete pd; //正确
delete pd2; //错误,pd2指向的内存已经被释放了
delete pi2; //正确释放一个空指针没有错

const int* pci = new const int(1024);
delete pci; //正确,释放一个const对象

在 delete 之后,指针就变成了空悬指针

int *p(new int(42)); //p指向动态内存
auto q = p; //p 和 q 指向相同的内存
delete p; //p 和 q 均变为无效
p = nullptr; //p不再绑定到任何对象
//q依赖是空悬指针

shared_ptr 和 new 结合使用

std::shared_ptr<double> p1; 
std::shared_ptr<int> p2(new int(42)); 

智能指针的构造函数是 explicit 的

std::shared_ptr<int> p1 = new int(1024); //错误,必须直接初始化 
std::shared_ptr<int> p2(new int(42));  //正确

std::shared_ptr<int> clone(int p){
	return std::shared_ptr<int>(new int(p))
}

请添加图片描述

不要混合使用普通指针和智能指针

因为普通指针指向的对象将会被智能指针销毁时释放

std::shared_ptr<int> p (new int(42)); //引用计数为1
process(p); //拷贝p会递增它的引用计数,在process中引用计数为2
int i = *p; //正确,引用计数为1

//用内置指针显式构造一个shared_ptr,这样做会导致错误
int* x(new int(1024)); //x是一个普通指针
process(x); //错误,不能将 int* 转换为 shared_ptr<int>
process(std::shared_ptr<int>(x)); //合法,但是内存将会被释放
int j = *x; //j未定义,因为 x 指向的内存已经被释放,x是空悬指针

get : 向不能使用智能指针的代码,传递一个内置指针

永远不要用 get 初始化一个智能指针或为另一个智能指针赋值

std::shared_ptr<int> p(new int(42)); //引用计数为1
int* q = p.get(); //正确,但使用q的时候要注意,不要让其管理的指针释放

{ //新程序块
//未定义:两个独立的shared_ptr指向相同的内存
	std::shared_ptr<int>(q);
}
int foo = *p //未定义的,p指向的内存已经被释放了

reset:更新引用计数,会释放 p 指向的对象

std::shared_ptr<int> p = new int(42); //错误,不能将一个指针赋予 shared_ptr
std::shared_ptr<int> p;
p.reset(new int(42)); //正确,p指向一个新对象
//-------------------------------------------------
std::shared_ptr<std::string> p;
if (!p.unique()){ //p.use_count为1,返回true,否则返回false
	p.reset(new std::string(*p)); //不是唯一使用者,分配新的拷贝给智能指针
}
*p += newVal; //是唯一对象的使用者,可直接改变对象的值

智能指针和异常

/*
	使用智能指针,即使程序块过早结束,也能正确释放内存
*/

void f(){
    std::shared_ptr<int> sp(new int(42)); //分配一个新对象
    //这段代码抛出一个异常,且 f()函数未捕获
}//在函数结束时,智能指针会自动释放内存

void f(){
    int *ip = new int(42); //动态分配对象
     //这段代码抛出一个异常,且 f()函数未捕获
    delete ip; //在退出前释放内存,这段释放内存的操作并未被执行
}

使用类似的技术来管理不具有良好定义的析构函数的类

请添加图片描述

请添加图片描述

12.1.3 unique_ptr 和 weak_ptr

unique_ptr : 独自拥有所指向的对象

std::unique_ptr<double> p1; //可以指向一个double的unique_ptr
std::unique_ptr<int> p2(new int(42)); //p2指向一个值为42的int

std::unique_ptr<std::string> p1(new std::string("HI"));
std::unique_ptr<std::string> p2(p1); //错误:unique_ptr不支持拷贝
std::unique_ptr<std::string> p3 = p1; //错误:unique_ptr不支持拷贝

请添加图片描述

unique_ptr 虽然不能拷贝或赋值 ,但可以通过 release 或 reset 将指针所有权从一个(非const)的转移到另一个

std::unique_ptr<std::string> p1(new std::string("HI"));
std::unique_ptr<std::string> p2(p1.release()); //release将p1置为空

std::unique_ptr<std::string> p3(new std::string("HI1"));
//将所有权从p3转移到p2
p2.reset(p3.release()); //释放原来指向的内存

不能拷贝的例外 : 可以拷贝或赋值一个将要销毁的 unique_ptr

std::unique_ptr<int> clone(int p){
	//正确: 从int* 创建一个 unique_ptr
	return std::unique_ptr<int>(new int(p));
}

std::unique_ptr<int> clone(int p){
	std::unique_ptr<int> ret(new int(p));
	return ret;
}

重写默认删除器
请添加图片描述

动态内存与智能指针

weak_ptr :绑定到 shared_ptr ,不会改变引用计数

请添加图片描述

auto p = make_shared<int>(42); 
weak_ptr<int> wp(p); //wp弱共享p,p的引用计数并未改变

//不能使用 weak_ptr 直接访问对象,必须调用 lock
if(shared_ptr<int> np = wp.lock()){ //如果np不为空
    //在if中, np 和 p 共享对象
}

展示 weak_ptr 用途

检查指针类:将为StrBlob 类定义一个伴随指针类

请添加图片描述

//对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr{
public:
    StrBlobPtr():curr(0){};
    StrBlobPtr(StrBlob & a,size_t sz = 0):wptr(a.data),curr(sz){};
    string& deref() const;
   	StrBlobPtr& incr(); //前缀递增
    
private:
    //若检查成功,check返回一个指向 vector 的 shared_ptr
    shared_ptr<vector<string>> check(size_t,const string&) const;
    
    //保存一个 weak_ptr,因为底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;//在数组当前的位置
}

shared_ptr<vector<string>> StrBlobPtr::check(size_t i,const string& msg) const{
    auto ret = wptr.lock(); //vector还存在吗?
    if(!ret) throw runtime_err("unbound StrBlobPtr");
    if(i>= ret->size()) throw out_of_range(msg);
    return ret; //否则,返回指向vector的shared_ptr<vector<string>>
}

string& StrBlobPtr::deref() const{
	auto p = check(curr,"derefernce past end");
	return (*p)(curr); //*p是对象所指的vector
}

StrBlobPtr& StrBlobPtr::incr(){
	//如果curr已经指向容器的尾后,就不能递增了
	check(curr,"increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

//对于友元声明来说,前置声明是必要的
class StrBlobPtr;
class StrBlob{
  friend class StrBlobPtr;
  //...
  //返回指向首元素和尾后元素的StrBlobPtr
  StrBlobPtr begin(){return StrBlobPtr(*this)};
  StrBlobPtr end(){
      auto ret =  StrBlobPtr(*this,data->size());    
      return ret;
  };
};

weak_ptr 打断 shared_ptr 的循环引用

std::weak_ptr :打断 std::shared_ptr 所管理的对象组成的环状引用。(打破shared_ptr的循环引用)

若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。

能令环中的指针之一为弱指针以避免此情况

#include <iostream>
#include <memory>
using namespace std;
 
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
 
class Child
{
public:
    ParentPtr father;
    Child() {
        cout << "hello Child" << endl;
    }
    ~Child() {
        cout << "bye Child\n";
    }
};
 
typedef std::shared_ptr<Child> ChildPtr;
 
class Parent {
public:
    ChildPtr son;
    Parent() {
        cout << "hello parent\n";
    }
    ~Parent() {
        cout << "bye Parent\n";
    }
};
 
void testParentAndChild()
{
    ParentPtr p(new Parent());
    ChildPtr c(new Child());
    p->son = c;
    c->father = p; 
}
 
int main()
{
    testParentAndChild();
    return 0;
}

一旦外部指向shared_ptr资源失效,那么weak_ptr管理的资源自动失效

#include <iostream>
#include <cassert>
#include <memory>
using namespace std;
 
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;
 
class Child
{
public:
    WeakParentPtr father;                 // 只要一环换成 weak_ptr, 即可打破环 
    Child() {
        cout << "hello Child" << endl;
    }
    ~Child() {
        cout << "bye Child\n";
    }
};
 
typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;
 
class Parent {
public:
    ChildPtr son;                 
    Parent() {
        cout << "hello parent\n";
    }
    ~Parent() {
        cout << "bye Parent\n";
    }
};
 
 
void testParentAndChild()
{
    ParentPtr p(new Parent());
    ChildPtr c(new Child());
    p->son = c;            
    c->father = p;        
    cout << (c->father).use_count() << endl;
    cout << (p->son).use_count() << endl;
}
 
int main()
{
    testParentAndChild();
    return 0;
}

12.2 动态数组

new 和数组

动态数组并不是数组类型,分配一个数组将会得到一个元素类型的指针

//通过get_size确定分配多少个int
int* pia = new int[get_size()]; //pia指向第一个int
//方括号中的大小必须是整数,但不必是常量

typedef int arrT[42]; //arrT表示42个int的数组类型
int *p = new arrT; //分配一个 42个int的数组,p 指向第一个int
//编译器将会执行 int *p = new int[42]; 

初始化动态分配对象的数组

int* pia1 = new int[10]; //10个未初始化的int
int* pia2 = new int[10](); //10个初始化为0的int
string* psa1 = new string[10]; //10个空字符串
string* psa2 = new string[10](); //10个空字符串

//3个int分别用列表中对应的初始化器初始化
int* pia3 = new int[3]{0,1,2};
//前4个初始化器初始化,剩余进行值初始化
string* psa3 = new string[10]{"A","B","C",string(3,'x')};

动态分配一个空数组是合法的

size_t n = get_size(); //get_size返回需要的元素数目,可以为0
int* p = new int[n]; //分配数组保存元素

for(int *q=p;q!=p+n;++q){
    //处理数组
}

char arr[0];//错误,不能定义长度为0的数组
char cp = new char[0]; //正确,但cp不能解引用

释放动态数组

delete p; //p必须指向一个动态分配的对象,或者为空
delete p[]; //pa必须指向一个动态分配的数组或为空
//数组中的元素按逆序销毁

智能指针和动态数组

//标准库提供了一个可以管理new分配数组的unique_ptr版本
unique_ptr<int[]> up(new int[10]);
up.release(); //自动销毁其管理的指针

for(size_t i=0;i!=10;++i){
	up[i] = i; //为每个元素赋值
}

请添加图片描述

shared_ptr 不直接支持管理动态数组

//如果希望使用 shared_ptr 管理,必须自定义删除器
shared_ptr<int> sp(new int[],[](int *p){delete[] p;}); 
sp.reset(); //使用提供的 lambda 释放数组,它使用 delete[]

//shared_ptr未定义下标运算符,并且不支持指针的算术运算
for(size_t i=0; i!=10; ++i){
    *(sp.get() +i) = i; //使用 get 获取一个内置指针
}

Allocator 类

将内存分配和对象构造分离

分配大块内存,但只在真正需要时才执行指向对象创建操作

//将内存分配和对象构造组合在一起可能导致不必要的浪费
string* const p = new string[n]; //构造 n 个空 string
string s;
string *q = p; //q指向第一个string
while( cin>>s && q != p+n ){
    *q++ = s; //赋予*q一个新值
}

const size_t size = q-p; //记录读取多少个string
//使用数组
delete[] p;  

请添加图片描述

Allocator 类分配的内存是原始的,未构造的

allcator<string> alloc; //可分配 string 的 allocator 对象
//分配一段原始的,未构造的内存
auto const p = alloc.allocate(n); //分配 n 个未初始化的string

auto q = p; //q指向最后构造的元素之后的位置
//在q指向的内存中构造一个对象
alloc.construct(q++);
alloc.construct(q++,10,'c');
alloc.construct(q++,"HI");

cout << *p << endl; //正确,使用string的输出运算符
cout << *q << endl; //错误,q指向未构造的内存

while(q != p){
    alloc.destroy(--q); //释放构造的string
    //只能对真正构造了的元素进行destroy操作
}

//先释放对象,再释放内存
alloc.deallocated(p,n);释放内存

标准库为 allocator 提供了两个伴随算法,可以在未初始化内存中创建对象

请添加图片描述

假定有一个 int 的 vector ,希望将其拷贝到动态内存中

//分别比 vi 中元素所占空间大一倍的动态内存
auto p = alloc.allocate(v.size()*2);

//通过拷贝 vi 中的元素来构造从 p 开始的元素
auto q = uninitialized_copy(v.begin(),v.end(),p);
//前两个参数表示输入序列,第三个参数为目的地空间
//与copy不同,uninitialized_copy在给定的位置上构造元素
//q指向最后一个构造的元素之后的位置

//将剩余的元素初始化为42
uninitialized_fill_n(q,v.size(),42);
举报

相关推荐

0 条评论