C++语法学习笔记
Posted Shi Peng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++语法学习笔记相关的知识,希望对你有一定的参考价值。
一、智能指针 unique_ptr
unique_ptr是C++ 11提供的用于防止内存泄漏的智能指针。
1.1、unique_ptr的实现原理
实现方式:
unique_ptr对象封装了一个原始指针,并管理了他的生命周期,即当该对象被销毁时,会自动在其析构函数中删除关联的原始指针。
例子:
#include <iostream>
#include <memory>
struct Task
int mId;
Task(int id ) :mId(id)
std::cout << "Task::Constructor" << std::endl;
~Task()
std::cout << "Task::Destructor" << std::endl;
;
int main()
// 通过原始指针创建 unique_ptr 实例
std::unique_ptr<Task> taskPtr(new Task(23));
//通过 unique_ptr 访问其成员
int id = taskPtr->mId;
std::cout << id << std::endl;
return 0;
输出:
Task::Constructor
23
Task::Destructor
可以看到,在main函数退出时,unique_ptr taskPtr的生命周期也结束了,这时,在unique_ptr对象taskPtr的析构函数中,会删除关联的原始指针,这样就不需要专门delete task对象了。
这样,无论函数是否正常退出(包括异常退出),都会调用taskPtr的析构函数,来删除原始指针,从而防止了内存泄漏。
1.2、unique_ptr独享对象所有权
unique_ptr对象始终是其原始指针的唯一所有者,即我们无法赋值unique_ptr对象,他只能移动。
正因为每个unique_ptr对象都是其原始指针的唯一所有者,因此在其析构函数中直接删除其原始指针,不需要任何计数。
1.3、unique_ptr的一些操作
1.3.1、创建一个空的unique_ptr对象
std::unique_ptr<int> ptr1;
因为没有与之关联的原始指针,所以他是空的。
1.3.2、检查unique_ptr对象是否为空
// 方法1
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
1.3.3、使用原始指针创建unique_ptr对象
方法1:
std::unique_ptr<Task> taskPtr(new Task(22));
方法2:
std::unique_ptr<Task> taskPtr(new std::unique_ptr<Task>::element_type(23));
1.3.4、使用std::make_unique 创建 unique_ptr 对象
std::make_unique<>() 是C++ 14 引入的新函数
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
1.3.5、获取被管理对象的指针
通过get()函数
Task *p1 = taskPtr.get();
1.3.6、重置 unique_ptr 对象
reset()函数将释放delete关联的原始指针,并使unique_ptr对象为空
taskPtr.reset();
1.3.7、unique_ptr 对象不可复制
由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。
// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error
1.3.8、转移 unique_ptr 对象的所有权 – 这做dir切换时可用到
我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。
例子:
// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 现在taskPtr2关联的指针为空
if(taskPtr2 == nullptr)
std::cout<<"taskPtr2 is empty"<<std::endl;
// taskPtr2关联指针的所有权现在转移到了taskPtr4中
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
// 会输出55
std::cout<< taskPtr4->mId << std::endl;
说明:std::move() 将把 taskPtr2 的所有权转移给taskPtr4, 转移后,taskPtr2变为空。
1.3.9、释放关联的原始指针
在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。
std::unique_ptr<Task> taskPtr5(new Task(55));
// 不为空
if(taskPtr5 != nullptr)
std::cout<<"taskPtr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = taskPtr5.release();
// 现在为空
if(taskPtr5 == nullptr)
std::cout<<"taskPtr5 is empty"<<std::endl;
说明:release()只是释放了所有权,并没有delete原始指针,指向的对象还在。
1.4、完整示例
#include <iostream>
#include <memory>
struct Task
int mId;
Task(int id ) :mId(id)
std::cout<<"Task::Constructor"<<std::endl;
~Task()
std::cout<<"Task::Destructor"<<std::endl;
;
int main()
// 空对象 unique_ptr
std::unique_ptr<int> ptr1;
// 检查 ptr1 是否为空
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 检查 ptr1 是否为空
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
// 不能通过赋值初始化unique_ptr
// std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr(new Task(23));
// 检查 taskPtr 是否为空
if(taskPtr != nullptr)
std::cout<<"taskPtr is not empty"<<std::endl;
// 访问 unique_ptr关联指针的成员
std::cout<<taskPtr->mId<<std::endl;
std::cout<<"Reset the taskPtr"<<std::endl;
// 重置 unique_ptr 为空,将删除关联的原始指针
taskPtr.reset();
// 检查是否为空 / 检查有没有关联的原始指针
if(taskPtr == nullptr)
std::cout<<"taskPtr is empty"<<std::endl;
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr2(new Task(55));
if(taskPtr2 != nullptr)
std::cout<<"taskPtr2 is not empty"<<std::endl;
// unique_ptr 对象不能复制
//taskPtr = taskPtr2; //compile error
//std::unique_ptr<Task> taskPtr3 = taskPtr2;
// 转移所有权(把unique_ptr中的指针转移到另一个unique_ptr中)
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 转移后为空
if(taskPtr2 == nullptr)
std::cout << "taskPtr2 is empty" << std::endl;
// 转进来后非空
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
std::cout << taskPtr4->mId << std::endl;
//taskPtr4 超出下面这个括号的作用于将delete其关联的指针
std::unique_ptr<Task> taskPtr5(new Task(66));
if(taskPtr5 != nullptr)
std::cout << "taskPtr5 is not empty" << std::endl;
// 释放所有权
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout << "taskPtr5 is empty" << std::endl;
std::cout << ptr->mId << std::endl;
delete ptr;
return 0;
输出结果:
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is empty
Task::Constructor
taskPtr2 is not empty
taskPtr2 is empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor
1.5、总结
1、new出来的对象是位于堆内存上的,必须调用delete才能释放其内存。
2、unique_ptr 是一个装指针的容器,且拥有关联指针的唯一所有权,作为普通变量使用时系统分配对象到栈内存上,超出作用域时会自动析构,unique_ptr对象的析构函数中会delete其关联指针,这样就相当于替我们执行了delete堆内存上的对象。
3、常用函数
成员函数 | 作用 |
---|---|
reset() | 重置unique_ptr为空,delete其关联的指针。 |
release() | 不delete关联指针,并返回关联指针。释放关联指针的所有权,unique_ptr为空。 |
get() | 仅仅返回关联指针 |
4、unique_ptr不能直接复制,必须使用std::move()转移其管理的指针,转移后原 unique_ptr 为空。std::unique_ptr taskPtr4 = std::move(taskPtr2);
二、new/delete用法
在C++中,new用于创建对象,delete用于销毁对象:
new对象时,会在堆中动态分配内存,而delete可把对象在堆中分配的内存释放掉。
在C++中,new和delete都不是函数,而是C++定义的关键字。
对于下面的语句:
string *ps = new string("hello world");
C++中的new和C中的malloc还是有点不同:
malloc申请空间后,不会对内存进行必要的初始化,而new会。
2.1、操作符new和delete
下面是C++标准库函数:
void *operator new(size_t); //allocate an object
void *operator delete(void *); //free an object
void *operator new[](size_t); //allocate an array
void *operator delete[](void *); //free an array
这两个函数和C语言中的malloc和free函数有点像,都是用来申请和释放内存的,并且operator new申请内存后,不会对内存进行初始化,而直接返回申请内存的指针。
下面通过一个例子来看new和delete背后的机制:
class A
public:
A(int v) : var(v)
fopen_s(&file, "test", "r");
~A()
fclose(file);
private:
int var;
FILE *file;
;
在此例子中,类A:
1)有两个私有变量:int类型的var 和 一个文件类型的变量
2)有一个构造函数,一个析构函数:
在构造函数中,初始化int变量,并打开一个文件;
在析构函数中,关闭打开的文件。
我们使用
class A *pA = new A(10);
来创建类A的实例,返回其指针pA。
此时,new所做的工作如下:
接着,我们看delete做了什么?
delete pA;
delete操作做了两件事:
1)调用A类的析构函数,对打开的文件进行关闭
2)通过标准库函数operator delete来释放该对象的内存。
以上是关于C++语法学习笔记的主要内容,如果未能解决你的问题,请参考以下文章