以前看过几次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