boost中的指针容器

boost中的指针容器

基本概念

在C++中用容器来管理动态分配的内存,首先想到是会通过标准容器来存储shared_ptr或者unique_ptr,如下代码:

#include <memory>   
#include <vector>   
int main()   
{   
  std::vector<std::shared_ptr<int> > v;   
  v.push_back(std::hared_ptr<int>(new int(1)));   
  v.push_back(std::shared_ptr<int>(new int(2)));   
}  

指针容器

boost库中专门提供了指针容器存储指针来管理动态分配的对象。标准库中vector,list,deque分别有ptr_vector,ptr_list,ptr_deque相对应,并且接口也与标准库中的容器一致。指针容器与容器+unique_ptr来管理动态分配的内存的语义是一致,对容器成员都是独占语义,不可复制,只可通过移动来转移所有权。不同的是指针容器直接操作的是裸指针。

具体可以看 boost文档说明

使用方法

指针容器与原容器的接口一直,如下示例

#include <iostream>
#include <boost/ptr_container/ptr_container.hpp>

int main()
{
    //以ptr_vector为例
    boost::ptr_vector<int> ints;
    //插入元素
    ints.push_back(new int(18));
    ints.push_back(new int(118));

    //以下标取元素
    std::cout << ints[0] << std::endl;
    //通过迭代器取元素
    std::cout << (*(ints.begin())) << std::endl;
    std::system("pause");
}

指针容器的内存释放

在指针容器会在析构时自动释放元素的内存,所以无需再额外的迭代释放每个成员的内存,如下:

#include <boost/ptr_container/ptr_container.hpp>  
#include <iostream>
#define _crtdbg_map_alloc
class CTest
{
public:
    CTest(int i):i(i)
    {
        std::cout <<"construct"<<std::endl;
    }
    ~CTest()
    {
        std::cout << "construct" << std::endl;
    }

    int i;
};

int main()
{
    
    {
        boost::ptr_vector<CTest> Tests;
        Tests.push_back(new CTest(1));
        Tests.push_back(new CTest(2));
    }
    
    //system("pause");  
    _CrtDumpMemoryLeaks();
    return 0;
}

运行可以看到是没有内存泄漏的。

容器成员的所有权

在容器析构时是会释放所有成员的内存。那么直接获取容器中的成员,就会涉及到所有权问题(谁来释放内存)。如果不注意就会造成重复释放内存的问题,如下示例代码:

int main()
{
    CTest *p = NULL;

    {
        boost::ptr_vector<CTest> Tests;

        Tests.push_back(new CTest(1));
        p = &Tests.front();
    }

    delete p;
    p = NULL;
    system("pause");
    return 0;
}

上面的代码会直接崩溃,是因为重复释放了p指针所指的内存(因为Tests析构时已经释放了容器内指针所指的内存),示例代码是为了演示问题而写的很直白,在实际的开发中,或许某个接口需要转入一个指针,而该指针又保存在某个指针容器中,那么此时就应该注意了,不小心就会写出如示例代码的错误。

通过release方法来获取元素的所有权

见如下代码,变量p就直接获取了Tests中第一个元素的所有权,那么Tests容器中就直接删除了第一个元素。

int main()
{
    boost::ptr_vector<CTest> Tests;
    Tests.push_back(new CTest(1));
    Tests.push_back(new CTest(2));

    boost::ptr_vector<CTest>::auto_type p = Tests.release(Tests.begin());

    return 0;
}

auto_type

指针容器中有一个特殊的auto_type,boost文档中对其的描述如下

You can think of auto_type as a non-copyable form of std::auto_ptr

我们知道std::auto_ptr是通过复制,赋值来实现的转移语义,这里的auto_type是没有复制语义的,但它有移动语义,也是通过release方法。这跟std::unique_ptr很像也是只有移动语义,也只可通过release方法来转移所有权。见如下代码:

int main()
{
    CTest *p1 = NULL;
    {
        boost::ptr_vector<CTest> Tests;
        Tests.push_back(new CTest(1));
        Tests.push_back(new CTest(2));

        boost::ptr_vector<CTest>::auto_type p = Tests.release(Tests.begin());

        p1 = p.release();
        if (!p)
        {
            std::cout << "no ownership" << std::endl;
        }
    }
    
    delete p1;
    p1 = NULL;

    //system("pause");
    _CrtDumpMemoryLeaks();
    return 0;
}

auto_type类型的变量p在将所有权转移给了p1后,如果不delete p1的话就会造成内存泄漏。

auto_type所有权的转移

上面提到auto_type类型是不可复制的,所以两个auto_type类型的变量,不能直接赋值来转移所有权,见如下代码:

int main()
{
    boost::ptr_vector<CTest> Tests;
    Tests.push_back(new CTest(1));
    Tests.push_back(new CTest(2));

    boost::ptr_vector<CTest>::auto_type p = Tests.pop_back();
    boost::ptr_vector<CTest>::auto_type p1;
    p1 = p;
    return 0;
}

上面的代码是无法编译通过的,应该通过ptr_container提供的move函数来进行所有权转移

p1 = boost::ptr_container::move(p);

应用示例

在muduo日志库中,AsyncLogging类中通过交换内存实现了异步日志,在类中分为前端和一个后端线程,前端用于收集格式化后的日志,后端线程负责将收集的日志写入文件,前端和后端的数据交换是通过交换内存来实现的。这里不讨论其异步日志实现思想,主要是学习下其内存交换的实现思路。

在AsynLogging类中,前端和后端持有两个块buffer。buffer的定义如下:

//Buffer的类型
typedef muduo::detail::FixedBuffer<muduo::detail::kLargeBuffer> Buffer;
//指针容器用于存储多块Buffer
typedef boost::ptr_vector<Buffer> BufferVector;
//Buffer指针,声明为auto_type类型
typedef BufferVector::auto_type BufferPtr;

前端线程定义的Buffer指针成员

//前端当前使用的缓存
BufferPtr currentBuffer_;
//前端待用的缓存
BufferPtr nextBuffer_;
//用于前后端线程交换缓存的buffers
BufferVector buffers_;

前端缓存的初始化,前端持有currentBuffer_和nextBuffer_两块内存

AsyncLogging::AsyncLogging(const string& basename,
                           size_t rollSize,
                           int flushInterval)
  : flushInterval_(flushInterval),
    running_(false),
    basename_(basename),
    rollSize_(rollSize),
    thread_(boost::bind(&AsyncLogging::threadFunc, this), "Logging"),
    latch_(1),
    mutex_(),
    cond_(mutex_),
    //初始化缓存
    currentBuffer_(new Buffer),
    //初始化缓存
    nextBuffer_(new Buffer),
    buffers_()
{
  currentBuffer_->bzero();
  nextBuffer_->bzero();
  buffers_.reserve(16);
}

前端线程交换缓存

void AsyncLogging::append(const char* logline, int len)
{
  muduo::MutexLockGuard lock(mutex_);
  if (currentBuffer_->avail() > len)
  {
    currentBuffer_->append(logline, len);
  }
  else
  {
    //当正在使用的缓存空间不够用时,则使用nextBuffer_缓存
    //将currentBuffer放入buffers_,准备作交换,注意这里currentBuffer_.release()即为放弃缓存所有权
    buffers_.push_back(currentBuffer_.release());

    if (nextBuffer_)
    {
      //将nextBuffer_的所有权转移给currentBuffer_(ptr_containts::auto_type不支持复制和赋值,只可通过move来转移所有权)
      currentBuffer_ = boost::ptr_container::move(nextBuffer_);
    }
    else
    {
      currentBuffer_.reset(new Buffer); // Rarely happens
    }
    currentBuffer_->append(logline, len);
    cond_.notify();
  }
}

后端线程交换缓存,后端持有newBuffer1和newBuffer2两块内存

void AsyncLogging::threadFunc()
{
  assert(running_ == true);
  latch_.countDown();
  LogFile output(basename_, rollSize_, false);
  //后端线程所持有的两个缓存
  BufferPtr newBuffer1(new Buffer);
  BufferPtr newBuffer2(new Buffer);
  newBuffer1->bzero();
  newBuffer2->bzero();
  //待写入文件的缓存
  BufferVector buffersToWrite;
  buffersToWrite.reserve(16);
  while (running_)
  {
    assert(newBuffer1 && newBuffer1->length() == 0);
    assert(newBuffer2 && newBuffer2->length() == 0);
    assert(buffersToWrite.empty());

    {
      muduo::MutexLockGuard lock(mutex_);
      if (buffers_.empty())  // unusual usage!
      {
        cond_.waitForSeconds(flushInterval_);
      }
      
      //后端线程被唤醒,后端线程获得lock锁
      //交换前端正在使用的缓存
      buffers_.push_back(currentBuffer_.release());
      //交换给currentBuffer一个新的缓存
      currentBuffer_ = boost::ptr_container::move(newBuffer1);
      //将待写入文件的日志交换给buffersToWrite
      buffersToWrite.swap(buffers_);
      if (!nextBuffer_)
      {
        //给nextBuffer_一块新的缓存
        nextBuffer_ = boost::ptr_container::move(newBuffer2);
      }
    }

    assert(!buffersToWrite.empty());

    if (buffersToWrite.size() > 25)
    {//写文件
      char buf[256];
      snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
               Timestamp::now().toFormattedString().c_str(),
               buffersToWrite.size()-2);
      fputs(buf, stderr);
      output.append(buf, static_cast<int>(strlen(buf)));
      buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
    }

    for (size_t i = 0; i < buffersToWrite.size(); ++i)
    {
      // FIXME: use unbuffered stdio FILE ? or use ::writev ?
      output.append(buffersToWrite[i].data(), buffersToWrite[i].length());
    }

    if (buffersToWrite.size() > 2)
    {
      // drop non-bzero-ed buffers, avoid trashing
      //指针容器跟标准库容器一样在resize后会释放多余的成员
      buffersToWrite.resize(2);
    }

    if (!newBuffer1)
    {
      assert(!buffersToWrite.empty());
      newBuffer1 = buffersToWrite.pop_back();
      newBuffer1->reset();
    }

    if (!newBuffer2)
    {
      assert(!buffersToWrite.empty());
      newBuffer2 = buffersToWrite.pop_back();
      newBuffer2->reset();
    }

    buffersToWrite.clear();
    output.flush();
  }
  output.flush();
}

如上代码,通过指针容器实现内存的交换十分优雅,即不会有内存拷贝的memecpy操作,也不用专门去考虑内存释放问题,实现的代码也十分简洁易读。

相关资料:
http://www.boost.org/doc/libs/1_66_0/libs/ptr_container/doc/tutorial.html#indirected-interface
http://www.boost.org/doc/libs/1_66_0/libs/ptr_container/doc/examples.html
https://stackoverflow.com/questions/871267/how-do-you-transfer-ownership-of-an-element-of-boostptr-vector/871510#871510

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo4776

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值