C++的内存管理

以前看过几次C++的内存管理,一直印象不够深刻,看来只有实际总结和操作一下才能更好的掌握。

自己管理内存的原因:

  自己管理内存通常是出于对性能的需求,需要重新定义内存的管理策略。因为缺省的内存管理策略,可能不能满足多种的内存需要。

基本知识(一):

 1、我们new一个对象时,要完成3件事:1)调用operator new申请内存; 2)调用对象构造函数;3)返回对象指针

 2、我们delete一个对象时,要完成2件事:1)调用析构函数;2)调用operator delete释放内存

有了上面两点,我们知道我们能够做的事情就是改变operator new和operator delete的操作,来改变类的内存管理;

下面我们来段程序热热身:

#include <stdio.h>
#include <stdlib.h>
class MemoryManager {
 public:
  void* operator new(size_t size) {
    printf("user memory allocator called\n");
    return malloc(size);    
  }
  void operator delete(void* handler) {
    printf("user memory free called\n");
    free(handler);
  }
 private:
    int data_;
};
int main(int argc, char** argv) {
  MemoryManager* memory_manager = new MemoryManager();
  delete memory_manager;
}

这个MemoryManager不能做什么事情,但演示了operator new 和 operator delete是如何工作的,需要特别注意的一点是operator new和operator delete都是静态函数,因为operator new在对象没有建立前就被调用,只能是静态的,因为此时还没有对象;而operator delete是在对象析构后被调用,此时也没有对象了,因此也应该时静态的,这就带来一个问题,operator new和operator delete只能访问类的静态成员(包括成员变量和成员函数)

基本知识(二)

 下面简单说说placement new,operator new返回的是void*, placement new不申请内存,他用一块内存地址构造一个对象,下面再试用一下placement new:

#include <stdio.h>
#include <stdlib.h>
#include <new>
class MemoryManager {
 public:
  static MemoryManager* MyNew(int data) {
    void* handler = malloc(sizeof(MemoryManager));
    // the following is placement new, to construct a instance
    MemoryManager* ret = new (handler) MemoryManager(data); //this is placement new
    return ret;
  }
  MemoryManager(int data) : data_(data) {}
  int GetData() {
    return data_;
  }
 private:
    int data_;
};
int main(int argc, char** argv) {
  MemoryManager * memory_manager = MemoryManager::MyNew(1);
  printf("%d\n", memory_manager->GetData());
  
}
注意#include <new>是必须的,否则编译会出错,这个问题还耽误了一点时间解决。
基本知识(三)

下面再来看看std::allocator,这个类是很多stl容器都会配置的一个参数,用来修改容器的内存分配策略,实际上它就是一个类,主要的方法如下:

1) allocator<T> a; //创建一个分配器对象

2) a.allocate(n) //申请一块内存

3) a.deallocate(p, n) //释放一块内存

4) a.construct(p,t) //在申请的内存上构造T的对象,调用构造函数

5) a.destory(p) //对p内存上的对象调用析构函数

std::allocate对于一些应用有重要的意义,其中一方面表现在它将内存的申请与对象的构建分离开来,这对于预先申请内存,但暂时不使用,未来也不一定会使用的情况,非常有利,如果用new申请内存,那么不管未来内存是否会使用,对象的构造函数都会被调用,开销比较大,而使用std::allocate则可以避免这种开销。下面,我们把std::allocate的使用,举一个还有点意义的例子,建立一个类似std::vector的向量,叫做MyVector,测试中,我们还对比了MyVector和std::vector的性能,通过调整capacity_的分配策略可以影响pk的结果。

#include <stdio.h>
#include <memory>
#include <string.h>
#include <vector>
#include <sys/time.h>
template<typename T>
class MyVector {
 public:
  MyVector() : elements_(NULL), first_free_(NULL), capacity_(0) {}
  ~MyVector() {
    if (elements_) {
      size_t elements_size = first_free_ - elements_;
      for (size_t i = 0; i < elements_size; ++i) {
        alloc_.destroy(elements_ + i);
      }
      alloc_.deallocate(elements_, capacity_);
    }
  }
  void PushBack(const T& value) {
    if (first_free_ - elements_ >= capacity_) {
      ReAllocate();
    }
    *first_free_ = value;
    ++first_free_;
  }
  size_t GetSize() {
    return first_free_ - elements_;
  }
  size_t GetCapacity() {
    return capacity_;
  }
  class Iterator {
   public:
    Iterator(T* elements, size_t offset) : elements_(elements), offset_(offset) {}
    T& operator*() {
      return *(elements_ + offset_);
    }
    T* operator->() {
      return (elements_ + offset_);
    }
    Iterator& operator++() {
      offset_++;
      return *this;
    }
    Iterator operator++(int) {
      Iterator tmp = *this;
      offset_++;
      return tmp;
    }
    Iterator operator--() {
      offset_--;
      return *this;
    }
    Iterator operator--(int) {
      Iterator tmp = *this;
      offset_--;
      return tmp;
    }
    friend bool operator!=(const Iterator& left, const Iterator& right) {
      return !(left == right);
    }
    friend bool operator==(const Iterator& left, const Iterator& right) {
      return (left.elements_ == right.elements_) && (left.offset_ == right.offset_);
    }
   private:
    T* elements_;
    size_t offset_;
  };
  Iterator Begin() {
    Iterator it(elements_, 0);
    return it;
  }
  Iterator End() {
    Iterator it(elements_, first_free_ - elements_);
    return it;
  }
 private:
  void ReAllocate() {
    capacity_ = (capacity_ == 0) ? 2 : (1.5 * capacity_);
    T* new_elements = alloc_.allocate(capacity_);
    size_t orginal_elements_size = first_free_ - elements_;
    memcpy(new_elements, elements_, orginal_elements_size * sizeof(T));
    for (size_t i = 0; i < orginal_elements_size; ++i) {
      alloc_.destroy(elements_ + i);
    }
    alloc_.deallocate(elements_, orginal_elements_size);
    elements_ = new_elements;
    first_free_ = elements_ + orginal_elements_size;       
  }
  size_t capacity_;
  T* elements_;
  T* first_free_;
  std::allocator<T> alloc_;
};
int GetTime() {
  timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec * 1000000 + tv.tv_usec;
}
int main(int argc, char** argv) {
  MyVector<int> my_vector;
  int size = 100;
  for (int i = 0; i < 100; ++i) {
    my_vector.PushBack(i);
    printf("insert:%d size:%zd capacity:%zd\n", i , my_vector.GetSize(), my_vector.GetCapacity());
  }
  for (MyVector<int>::Iterator it = my_vector.Begin(); it != my_vector.End(); ++it) {
    printf("value = %d\n", *it);
  }
  
  //speek pk with std::vector
  std::vector<int> std_vector;
  MyVector<int> my_vector1;
  int start;
  int end;
  size = 1000000;
  start = GetTime();
  for (int i = 0; i < size; ++i) {
    std_vector.push_back(i);
  }
  end = GetTime();
  printf("std:%d\n", end - start);
  start = GetTime();
  for (int i = 0; i < size; ++i) {
    my_vector1.PushBack(i);
  }
  end = GetTime();
  printf("my vector:%d\n", end - start);
}

最后来一个链表缓冲池的完整例子,来自c++ primer

#include <stdio.h>
#include <memory>
#include <stdexcept>
#include <string>
#include <sys/time.h>
template<typename T>
class CacheBase {
 public:
  CacheBase() : next_(NULL) {}
  void* operator new(size_t size);
  void operator delete(void* handler, size_t size);
  virtual ~CacheBase() {
  }
 private:
  static void AddFreeObject(T* object);
  static const int chunk_;
  static T* free_store_;
  static std::allocator<T> alloc_;
  T* next_; 
};
template<typename T>
std::allocator<T> CacheBase<T>::alloc_;
template<typename T>
T* CacheBase<T>::free_store_  = NULL;
template<typename T>
const int CacheBase<T>::chunk_ = 240000;
template<typename T>
void* CacheBase<T>::operator new(size_t size) {
  if (size != sizeof(T)) {
    throw std::runtime_error("wrong object size");
  }  
  if (!free_store_) {
    T* new_object = alloc_.allocate(chunk_);
    for (int i = 0; i < chunk_; ++i) {
      AddFreeObject(new_object + i);
    }
  }
  T* ret = free_store_;
  // CacheBase's Derived class may have next_ member, so we use CacheBase<T>::next_ to indicate the next_ in the CacheBase class
  free_store_ = free_store_->CacheBase<T>::next_;
  return ret;
}
template<typename T>
void CacheBase<T>::operator delete(void* handler, size_t size) {
  if (handler) {
    AddFreeObject(static_cast<T*>(handler));
  }
}
template<typename T>
void CacheBase<T>::AddFreeObject(T* object) {
  object->CacheBase<T>::next_ = free_store_;
  free_store_ = object;
}
class CachedBook : public CacheBase<CachedBook> {
 public:
  void fun1(const std::string& value) {
    book_name_ = value;
}
  void fun2() {}
  CachedBook() : book_name_("000"), book_id_(0) {}
 private:
  std::string book_name_;
  int book_id_;
};
class UnCachedBook {
 public:
  void fun1(const std::string& value) {
    book_name_ = value;
  }
  void fun2() {}
  UnCachedBook() : book_name_("000"), book_id_(0) {}
 private:
  std::string book_name_;
  int book_id_;
};
int GetTime() {
  timeval tv;
  gettimeofday(&tv, NULL);
  int ret= tv.tv_sec * 1000000 + tv.tv_usec;
  return ret;
}
int main(int argc, char** argv) {
  int size = 500000000;
  int start;
  int end;
  start = GetTime();
  for (int i = 0; i < size; ++i) {
    CachedBook* cached_book = new CachedBook();
    delete cached_book;
  }
  end = GetTime();
  printf("time: %d\n", end - start);
  start = GetTime();
  for (int i = 0; i < size; ++i) {
    UnCachedBook* uncached_book = new UnCachedBook();
    delete uncached_book;
  }
  end = GetTime();
  printf("time: %d\n", end -start);


  return 1;
}



CacheBase一定要定义成模版类,主要是因为申请内存时要申请子类大小的对象,因此子类一定要作为模版参数传入,否则CacheBase不会知道创建对象的大小。实测的结果觉得差别不是特别大,5亿次申请释放,效率在我的笔记本上差别大概在10秒左右。如果编译时使用google-perftool的tc-malloc,两者的性能基本相同。

参考文献

c++ primer中文第4版18.1章节

http://cissco.iteye.com/blog/379093

 








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值