当前位置:网站首页>手把手教你实现buffer(二)——内存管理及移动语义
手把手教你实现buffer(二)——内存管理及移动语义
2022-08-04 05:25:00 【mo4776】
在webrtc中有一个
Buffer
类,它是一个非常典型的基础buffer封装,我们来通过分析它的实现,来学习要如何实现一个buffer。这篇文件介绍 Buffer
的构造函数及移动语义。 webrtc中的Buffer类
在前面的文章中,提到了buffer的几个基本功能:
- 内存动态分配
- 自动管理内存
- 自动扩容
- 提供使用方便的接口
我们从这4个方面来分析Buffer
类的实现
class Buffer:public noncopyable {
public:
Buffer():_size(0),_capacity(0),_data(nullptr){
}
Buffer(size_t size,size_t capacity):
_size(size),
_capacity(std::max(size,capacity)),
_data(_capacity>0?new uint8_t[_capacity]:nullptr) {
}
Buffer(size_t size):Buffer(size,size) {
}
Buffer(const uint8_t* data, size_t size, size_t capacity):Buffer(size,capacity) {
std::memcpy(_data.get(),data, size);
}
Buffer(const uint8_t* data,size_t size):Buffer(data,size,size){
}
Buffer(Buffer&& buf):
_size(buf.size()),
_capacity(buf.capacity()),
_data(std::move(buf._data)) {
assert(IsConsistent());
buf._size = 0;
buf._capacity = 0;
}
Buffer& operator=(Buffer&& buf) {
assert(IsConsistent());
_size = buf._size;
_capacity = buf._capacity;
std::swap(_data,buf._data);
buf._data.reset();
buf._size = 0;
buf._capacity = 0;
return *this;
}
bool empty() const {
return _size == 0;
}
uint8_t* data() {
return _data.get();
}
uint8_t* data() const {
return _data.get();
}
size_t size() const {
return _size;
}
size_t capacity() const {
return _capacity;
}
uint8_t& operator[](size_t index) {
return data()[index];
}
uint8_t operator[](size_t index) const {
return data()[index];
}
void SetData(const uint8_t* data,size_t size);
void AppendData(const uint8_t* data,size_t size);
void SetSize(size_t size);
private:
void ZeroTrailingData(size_t count);
void EnsureCapacityWithHeadroom(size_t capacity, bool extra_headroom);
bool IsConsistent() const;
private:
size_t _size;
size_t _capacity;
std::unique_ptr<uint8_t[]> _data;
};
}
我做了点小的改良,Buffer
类原本是个模版类,这里我将的类型固化为uint8_t
便于分析。
管理内存
- 在
Buffer
内部通过unique_ptr
来管理内存空间,这里是uint8_t[]
,因为是管理的一段内存。
std::unique_ptr<uint8_t[]> _data;
- 在构造函数中分配内存。
//传入size和capacit来分配一段内存
Buffer(size_t size,size_t capacity):
_size(size),
_capacity(std::max(size,capacity)),
_data(_capacity>0?new uint8_t[_capacity]:nullptr) {
}
//只传入一个size参数时,size合capacity大小一直
Buffer(size_t size):Buffer(size,size) {
}
也可以通过一段内存构造Buffer
//分配制定size和capacity的内存并将data的内存copy到buffer中
Buffer(const uint8_t* data, size_t size, size_t capacity):Buffer(size,capacity) {
std::memcpy(_data.get(),data, size);
}
Buffer(const uint8_t* data,size_t size):Buffer(data,size,size){
}
- 在析构函数中释放内存
因为使用的是unique_ptr
,所以直接使用默认析构函数即可,在析构_data
时,内存会自动释放。
给Buffer赋予语意
在C++中的对象有三种语意:值语义,引用语义,移动语义
- 值语义,支持拷贝,指对象的拷贝与原对象无关,就像内置类型一样,将一个int型变量赋值给另外一个变量,两者间是无关联的。
- 引用语义,支持拷贝,指对象的拷贝与原对象相关联,比如在内部有指针的对象,如果对象赋值时对指针是浅拷贝,这里两个对象是相互关联的,因为两个对象的指针都指向同一块内存。
- 移动语义,不支持拷贝,两个对象间不能相互赋值,只能将内部资源移动到另外一个对象,原对象则变为无效对象。
Buffer
在C++中最直观的语义就是引用语义,因为它代表一块内存空间。多个裸指针可以指向同一块内存,理论上Buffer
也应该支持引用。
但是这种引用语义,很难区分所有权,即是谁需要对这块内存负责(负责分配,负责释放),这种语义的Buffer
多引用几次,引用关系就会混乱了,很容易造成问题,比如内存无法释放;内存在释放后还会被访问造成段错误等。
移动语意刚刚与引用语义相反,它不支持拷贝,不能相互引用,只存在移动操作。对**Buffer**
来说的移动语义,它所代表的资源就是内部的内存空间,将一个buffer1对象移动到另外一个buffer2对象,就是代表在buffer1对象不再拥有这段内存空间,而是将其转移给了buffer2对象。
这样严格区分所有权,可以转让出所有权,但是不能共享所有权。这样就能保证内存空间能被正常使用和安全的释放。
禁止拷贝
禁止拷贝,通过是构造函数,复制构造函数,赋值操作符号来实现。要禁用复制语义,需要即不定义这些函数也不能让编译器自动生成。如下是Buffer
类的实现方法,继承noncopyable
,
class Buffer:public noncopyable
nocopyable
的实现
class noncopyable {
public:
noncopyable(const noncopyable&) = delete;
void operator = (const noncopyable&) = delete;
protected:
noncopyable() = default;
~noncopyable() = default;
};
nocopyable
将拷贝构造函数和赋值函数声明为delete,构造函数和析构函数声明为private,就无法产生**nocoyable**
对象。而**Buffer**
继承**nocopyable**
也就禁用了拷贝,这是实现禁止拷贝的通用手法。
实现移动语义
C++11中自动类型具有移动语义,需要实现移动构造函数和移动赋值操作符,通过它们来定义移动的行为。
移动构造函数
//移动构造函数
Buffer(Buffer&& buf):
_size(buf.size()),
_capacity(buf.capacity()),
_data(std::move(buf._data)) {
assert(IsConsistent());
buf._size = 0;
buf._capacity = 0;
}
将形参的_data
指向的内存空间通过std::move
给到了所构造的对象,而原对象的_data
被置为无效,_size
和_capacity
也被置为0。
移动赋值操作符
//移动赋值运算符
Buffer& operator=(Buffer&& buf) {
assert(IsConsistent());
_size = buf._size;
_capacity = buf._capacity;
std::swap(_data,buf._data);
buf._data.reset();
buf._size = 0;
buf._capacity = 0;
return *this;
}
移动赋值运算符的逻辑跟移动构造函数的相同,将资源转移,将原对象置为无效。
使用Buffer
结合构造函数及移动语义,那么Buffer
就可以这样用
Buffer tmpF() {
uint8_t tmp[5] = {
1,2,3,4,5};
Buffer tmpB(tmp,5);
return tmpB;
}
int main() {
Buffer buffer1;//1
uint8_t b[3] = {
1,2,3};
Buffer buffer2(b,3);
std::cout<<"buffer1 size "<<buffer1.size()<<",buffer2 size "<<buffer2.size()<<std::endl;
Buffer buffer3(std::move(buffer2));//2
std::cout<<"buffer3 size "<<buffer3.size()<<",buffer2 size "<<buffer2.size()<<std::endl;
Buffer buffer4 = tmpF();
std::cout<<"buffer4 size "<<buffer4.size()<<std::endl;
}
- 首先构造了两个
Buffer
对象buffer1和buffer2,buffer1的size为0,buffer2的size为3。 - 通过移动构造函数使用buffer2构造了对象buffer3,此时buffer2的资源转移至buffer3,buffer3的size为3,buffer2的size为0。
- 函数
tmpF()
返回一个临时对象通过移动构造函数来构造buffer4,临时对象的资源被转移到buffer4,buffer4的size为5。
边栏推荐
猜你喜欢
Get the selected content of the radio box
sql server如何得到本条记录与上一条记录的差异,即变动值
The cost of automated testing is high and the effect is poor, so what is the significance of automated testing?
MySQL log articles, binlog log of MySQL log, detailed explanation of binlog log
idea设置识别.sql文件类型以及其他文件类型
7.15 Day21---MySQL----Index
Unity行为树AI分享
程序员也应了解的Unity粒子系统
7.16 Day22---MYSQL(Dao模式封装JDBC)
FPGA学习笔记——知识点总结
随机推荐
力扣题解8/3
[One step in place] Jenkins installation, deployment, startup (complete tutorial)
npm init [email protected] 构建项目报错SyntaxError: Unexpected token ‘.‘解决办法
高性能高可靠性高扩展性分布式防火墙架构
7. Execution of special SQL
CentOS7 - yum install mysql
5个开源组件管理小技巧
OpenCV获取和设置图像的平均亮度
触觉智能分享-SSD20X实现升级显示进度条
4.2 声明式事务概念
4.3 Annotation-based declarative transactions and XML-based declarative transactions
7.15 Day21---MySQL----Index
力扣:63. 不同路径 II
warning C4251: “std::vector&lt;_Ty&gt;”需要有 dll 接口由 class“Test”的客户端使用错误
SLSA 框架与软件供应链安全防护
JNI基本使用
Canal mysql data synchronization
el-Select selector bottom fixed
Several ways to heavy
入坑软件测试的经验与建议