C++ Primer笔记(十八)优化内存分配
2014-04-27 11:33
302 查看
1、C++类型分配是一个类型化操作:new特定类型分配内存,并在新分配的内存中构造对象。new表达式会为每个动态分配的类自动调用构造函数。但有些时候需要将内存分配与对象构造分开,因为对预先分配,但以后却不使用的对象进行构造很是浪费的。
2、内存分配和对象构造紧密纠缠,就像对象析构和内存回收一样。new表达式分配内存并在该内存中构造一个对象,delete表达式调用析构函数撤销对象,并将对像所用内存返还给系统。
C++提供两种方法,分配和释放未构造的的原始内存:
1)allocator类,它提供可感知类型的内存分配。这个类使用allocate成员分配内存,使用deallocate成员释放内存。
2)标准库中的operator new 和operator delete,它们分配和释放需要大小的原始的、未类型化的内存。
3、C++还提供不同的方法,在原始内存中构造和撤销对象。
1)allocator类定义了名为construct和destroy的成员,其操作正如它们的名字所指出的那样。construct成员在未构造的内存中调用复制构造函数初始化对象。destroy成员在对象上运行适当的析构函数。
2)定位new表达式。接受指定未构造内存的指针,并在该空间初始化一个对象或数组。
3)可以直接调用对象的析构函数来撤销对象。运行析构函数不释放对象所在的内存。
4)算法uninitialized_fill和uninitialized_copy与fill和copy类似。只是它们在给定地址调用复制构造函数构造对象。
4、现代C++一般应该使用allocator来分配内存。它更安全更灵活。但是在构造对象时用new表达式比allocator::construct成员更灵活。且有几种情况必须使用new。
5、allocator类是一个模板,它将内存的分配和对象构造分开。
当allocator对象分配内存的时候,它分配适当大小指定类型对象的空间。但是它分配的空间是未构造的,allocator的用户必须分别使用construct和destroy构造和析构对象。
allocator<T> a定义名为a的allocator对象,它用于分配内存或构造T类型的对象。
allocate(n); 分配原始的、未构造的内存,保存T类型的n个对象。
deallocate(p,n); 释放内存,在类型为T*的指针p指向的地址,保存着n个对象,运行deallocate之前调用destroy是用户的责任。
a.contruct(p,t); 在T*类型指针p所指向的内存中构造一个新元素。运行T类型的复制构造函数用t初始化该对象。
a.destroy(p); 运行T*类型指针p所指向对象的析构函数。
uninitialized_fill(b,e,t); 将由迭代器b和e标记的范围的对象,初始化为t的副本,它是用复制构造函数构造对象。
uninitiated_copy(b,e,b2) 从迭代器b和e指出的输入范围,将元素复制到从迭代器b2开始的,未构造的原始内存中。该函数在目的地构造元素,而不是给它们赋值。假定b2指出的目的地址足以保存输入范围中元素的副本。
6、当使用new操作符时,实际发生了三个步骤:
1)调用名为operator new的标准库函数。分配足够大的原始的未类型化的内存,以保存指定类型的对象。
2)运行该类型的一个构造函数,用指定初始化式构造对象。
3)返回指向新分配并构造对象的指针。
使用delete操作符时,发生两个步骤:
1)对指向的对象运行析构函数。
2)调用名为operator delete的标准库函数释放该对象的内存。
7、要注意分清new表达式和标准库的operator new函数。
Operator new和operator delete有两个不同的版本。每个版本支持相关的new表达式和delete表达式。
void *operator new(size_t);
vodi *operator new [](size_t);
void operator delete(void*);
void operator delete[](void*);
通常operator new和operator delete的设计意图,是供new和delete表达式使用,但是我们仍然可以使用它们获得未构造的内存。这与allocator的allocate和deallocate功能相同。如:
T* newelements=alloc.allocate(num);
1)用operator new替代为:
T*newelements=static_cast<T*> (operator new[] (num*sizeof(T));
alloc.deallocate(elements,end-elements);
2)用operator delete替换为:
operator delete[ ](elements);
注意:operator new和operator delete与allocate和deallocate的区别就是:它们在void*的指针上进行操作。而allocate和deallocate在类型为T的指针上进行操作。
8、allocator分配类型化的内存,无须转换因此allocator比直接使用operator new和operator更为类型安全。
定位new表达式在已分配的原始内存中初始化一个对象。
它与new的其他版本的不同之处在于:它不分配内存。它的形式为:
new(地址)类型
new(地址)类型(初始化表)
初始化表是在构造新分配的对象时使用的。
Alloc.construct(first_free,t);
用定位new替代为:new(first_free)T(t);
9、使用定位new表达式比使用allocator类的construct成员更灵活。
因为定位new在初始化一个对象的时候,它可以使用任何构造函数,而construct函数总是使用复制构造函数。
析构函数可以被显式调用,如p->~T();
它调用类型T的析构函数,适当的清楚对象本身,但是没有释放对象所占内存。
注意:调用operator delete不会运行析构函数,它只释放指定内存。
10、默认情况下new表达式通过由标准库定义的operator new版本分配内存,通过自定义的名为operator new和operator delete的成员函数,类可以管理应用于自身类型的内存。
编译器在看到类类型的new或delete表达式时,它查看该类是否有operator new或operator delete成员。如果该类定义或继承了自己的operator new和operator delete函数,则使用它们为对象分配和释放内存。否则调用标准库的版本。
自定义的operator new和operator delete默认为静态的。不必显式的声明为static,编译器默认将它们视为static函数。因为它们要么在构造对象之前使用,要么在撤销对象之后使用,因此,这些函数不依赖类的对象而存在。
[cpp] view
plaincopy
classTest
{
public:
Test()
{
}
staticvoid *operatornew(size_tnum)//num表示要分配空间的字节数。
{
}
staticvoidoperator delete(void *s)//s为要删除的指针。
{
}
//或者
staticvoidoperator delete(void *s,size_t n) s为要删除的指针。n为s所指向对象的字节。
{
}
};
当为operator delete提供size_t形参时,就由编译器用第一个形参所指对象的字节大小,自动初始化size_t形参。
当类是某继承层次的一部分时,这是必需的。因为指针既可以指向基类对象,又可以指向派生类对象。
派生类的对象大小一般比基类对象要大,如果基类有virtual析构函数,则传给operator delete的大小,将根据被删除指针所指向对象的动态类型而变化。如果基类没有virtual析构函数,通过基类指针删除指向派生类对象的行为,是未定义的。
11、同样也可以定义成员operator new[]和operator delete[]来管理类类型的数组。如果这些函数存在,编译器就是用它们代替标准库的版本。
当类定义了自己的operator new和operator delete,标准库的operator new和operator delete就被屏蔽。
但是可以通过全局作用域操作符强制new或delete表达式使用全局的库函数。如:
T*p=::new T;
::delete p;
以下为自定义Vector类:
[cpp] view
plaincopy
#include<iostream>
using namespace std;
#include<algorithm>
#include<stdexcept>
template<typename T>
class Vector
{
public:
Vector()
{
first_free=NULL;
end=NULL;
element=NULL;
}
void push_back(T t)
{
if(first_free==end)
{
reallocate();
}
//alloc.construct(first_free,t);//调用构造函数。
new (first_free)T(t);
first_free++;
}
T &operator[](size_t s)
{
if(s>first_free-element)
{
throw out_of_range("下标越界啦!!");
}
else
{
return element[s];
}
}
const T &operator[](size_t s)const
{
if(s>first_free-element)
{
throw out_of_range("下标越界啦!!");
}
else
{
return element[s];
}
}
private:
void reallocate()
{
int num=first_free-element;
T*newelement;
if(num>0)
{
newelement=alloc.allocate(2*num);
//newelement=static_cast<T*>(operator new(2*num*sizeof(T)));//调用operator new申请空间。
uninitialized_copy(element,first_free,newelement);//将原来空间的元素复制到新地址。调用复制构造函数。
}
else
{
newelement=alloc.allocate(2);
//newelement=static_cast<T*>(operator new(2*sizeof(T)));//调用operator new申请空间。
}
for(T*i=element;i!=first_free;i++)
{
alloc.destroy(i);//调用析构函数。
//i->~T();//显式调用析构函数。
}
alloc.deallocate(element,end-element);//释放空间。
//operator delete (element);//使用operator delete释放空间。
//更新个指针。
element=newelement;
first_free=element+num;
if(num==0)
{
end=element+2;
}
else
{
end=element+2*num;
}
}
private:
allocator<T> alloc;//为什么此处加上static就会报链接错误呢。
T*newelement;
T* first_free;
T*end;
T*element;
};
int main(int argc,char**argv)
{
Vector<int> vi;
try
{
for(int i=0;i<20;i++)
vi.push_back(i+5);
for(int i=0;i<20;i++)
{
cout<<i+1<<": "<<vi[i]<<endl;
}
cout<<vi[28]<<endl;
}
catch (exception&e)
{
cout<<e.what()<<endl;
}
return 0;
}
12、接下来实现一个内存分配器基类。
1)它预先分配一块原始内存,来保存未构造的对象。创建新元素时,可以在预先分配的内存中构造,释放元素时将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这种策略常被成为维持一个自由列表。本例将自由列表实现为已分配但未构造的对象的链表。
2)有可能很多类都需要使用自由列表的分配策略。因此任何需要这种策略的类,都可以直接继承自这个类。因为这个类希望为任意类型服务,所以它被定义为类模板。
3)它有几个简单接口:
重写的operator new成员函数:new操作符被调用时调用此函数。它从自由列表中取走一个未构造的元素。因为new操作符的第二步会调用定位new操作符构造元素。Operator delete成员函数,它将要删除的指针指向的对象添加到自由列表中而不是返还给系统。
注意:此类只能用于包含在继承层次中的类型。因为它无法根据对象的实际类型,分配不同大小的对象。它的自由列表存储单一大小的对象。
4)使用allocator分配空间。它是静态的。自由列表头指针和其他一些变量都是被声明为static的成员变量,因为我们希望为所有相同类型的对象维持一个自由列表。
5)addToFreeList用于将一个指针指向的未构造或析构过的空间加入自由列表。next指针指向此类的派生类对象。因为自由列表存储的就是派生类对象的链表。
具体实现见代码:
#include<iostream>
#include<stdexcept>
using namespace std;
template<typename T>
class MemoryAllocate
{
public:
MemoryAllocate()
{
}
void *operator new(size_t s)
{
if(s!=sizeof(T))
throw runtime_error("传入的类型大小不符!");
if(!freeList)
{
T*array=alloc.allocate(num);
for(size_t i=0;i<num;i++)
{
addToFreeList(array+i);
}
}
T*p=freeList;
freeList=freeList->next;
return p;
}
void operator delete(void*s)
{
T*p=static_cast<T*>(s);
addToFreeList(p);
cout<<"wobeidiaoyongle "<<endl;//这句话被输出了,说明此函数被调用了。但是为何无法跟进呢???20120530 21:22
}
virtual ~MemoryAllocate()
{
}
private:
static void addToFreeList(T*t)
{
t->next=freeList;
freeList=t;
}
private:
static T*freeList;
static T*next;
static allocator<T> alloc;
static size_t num;
};
template<typename T> T*MemoryAllocate<T>::freeList=NULL;
template<typename T> size_t MemoryAllocate<T>::num=10;
template<typename T> T*MemoryAllocate<T>::next=NULL;
template<typename T> allocator<T> MemoryAllocate<T>::alloc;
class Derived:public MemoryAllocate<Derived>
{
public:
Derived()
{
i=0;
}
int i;
};
int main(int argc,char**argv)
{
try
{
Derived *mai=new Derived;
Derived *p=new Derived;
delete mai;
delete p;
}
catch (exception& e)
{
cout<<e.what()<<endl;
}
return 0;
}
2、内存分配和对象构造紧密纠缠,就像对象析构和内存回收一样。new表达式分配内存并在该内存中构造一个对象,delete表达式调用析构函数撤销对象,并将对像所用内存返还给系统。
C++提供两种方法,分配和释放未构造的的原始内存:
1)allocator类,它提供可感知类型的内存分配。这个类使用allocate成员分配内存,使用deallocate成员释放内存。
2)标准库中的operator new 和operator delete,它们分配和释放需要大小的原始的、未类型化的内存。
3、C++还提供不同的方法,在原始内存中构造和撤销对象。
1)allocator类定义了名为construct和destroy的成员,其操作正如它们的名字所指出的那样。construct成员在未构造的内存中调用复制构造函数初始化对象。destroy成员在对象上运行适当的析构函数。
2)定位new表达式。接受指定未构造内存的指针,并在该空间初始化一个对象或数组。
3)可以直接调用对象的析构函数来撤销对象。运行析构函数不释放对象所在的内存。
4)算法uninitialized_fill和uninitialized_copy与fill和copy类似。只是它们在给定地址调用复制构造函数构造对象。
4、现代C++一般应该使用allocator来分配内存。它更安全更灵活。但是在构造对象时用new表达式比allocator::construct成员更灵活。且有几种情况必须使用new。
5、allocator类是一个模板,它将内存的分配和对象构造分开。
当allocator对象分配内存的时候,它分配适当大小指定类型对象的空间。但是它分配的空间是未构造的,allocator的用户必须分别使用construct和destroy构造和析构对象。
allocator<T> a定义名为a的allocator对象,它用于分配内存或构造T类型的对象。
allocate(n); 分配原始的、未构造的内存,保存T类型的n个对象。
deallocate(p,n); 释放内存,在类型为T*的指针p指向的地址,保存着n个对象,运行deallocate之前调用destroy是用户的责任。
a.contruct(p,t); 在T*类型指针p所指向的内存中构造一个新元素。运行T类型的复制构造函数用t初始化该对象。
a.destroy(p); 运行T*类型指针p所指向对象的析构函数。
uninitialized_fill(b,e,t); 将由迭代器b和e标记的范围的对象,初始化为t的副本,它是用复制构造函数构造对象。
uninitiated_copy(b,e,b2) 从迭代器b和e指出的输入范围,将元素复制到从迭代器b2开始的,未构造的原始内存中。该函数在目的地构造元素,而不是给它们赋值。假定b2指出的目的地址足以保存输入范围中元素的副本。
6、当使用new操作符时,实际发生了三个步骤:
1)调用名为operator new的标准库函数。分配足够大的原始的未类型化的内存,以保存指定类型的对象。
2)运行该类型的一个构造函数,用指定初始化式构造对象。
3)返回指向新分配并构造对象的指针。
使用delete操作符时,发生两个步骤:
1)对指向的对象运行析构函数。
2)调用名为operator delete的标准库函数释放该对象的内存。
7、要注意分清new表达式和标准库的operator new函数。
Operator new和operator delete有两个不同的版本。每个版本支持相关的new表达式和delete表达式。
void *operator new(size_t);
vodi *operator new [](size_t);
void operator delete(void*);
void operator delete[](void*);
通常operator new和operator delete的设计意图,是供new和delete表达式使用,但是我们仍然可以使用它们获得未构造的内存。这与allocator的allocate和deallocate功能相同。如:
T* newelements=alloc.allocate(num);
1)用operator new替代为:
T*newelements=static_cast<T*> (operator new[] (num*sizeof(T));
alloc.deallocate(elements,end-elements);
2)用operator delete替换为:
operator delete[ ](elements);
注意:operator new和operator delete与allocate和deallocate的区别就是:它们在void*的指针上进行操作。而allocate和deallocate在类型为T的指针上进行操作。
8、allocator分配类型化的内存,无须转换因此allocator比直接使用operator new和operator更为类型安全。
定位new表达式在已分配的原始内存中初始化一个对象。
它与new的其他版本的不同之处在于:它不分配内存。它的形式为:
new(地址)类型
new(地址)类型(初始化表)
初始化表是在构造新分配的对象时使用的。
Alloc.construct(first_free,t);
用定位new替代为:new(first_free)T(t);
9、使用定位new表达式比使用allocator类的construct成员更灵活。
因为定位new在初始化一个对象的时候,它可以使用任何构造函数,而construct函数总是使用复制构造函数。
析构函数可以被显式调用,如p->~T();
它调用类型T的析构函数,适当的清楚对象本身,但是没有释放对象所占内存。
注意:调用operator delete不会运行析构函数,它只释放指定内存。
10、默认情况下new表达式通过由标准库定义的operator new版本分配内存,通过自定义的名为operator new和operator delete的成员函数,类可以管理应用于自身类型的内存。
编译器在看到类类型的new或delete表达式时,它查看该类是否有operator new或operator delete成员。如果该类定义或继承了自己的operator new和operator delete函数,则使用它们为对象分配和释放内存。否则调用标准库的版本。
自定义的operator new和operator delete默认为静态的。不必显式的声明为static,编译器默认将它们视为static函数。因为它们要么在构造对象之前使用,要么在撤销对象之后使用,因此,这些函数不依赖类的对象而存在。
[cpp] view
plaincopy
classTest
{
public:
Test()
{
}
staticvoid *operatornew(size_tnum)//num表示要分配空间的字节数。
{
}
staticvoidoperator delete(void *s)//s为要删除的指针。
{
}
//或者
staticvoidoperator delete(void *s,size_t n) s为要删除的指针。n为s所指向对象的字节。
{
}
};
当为operator delete提供size_t形参时,就由编译器用第一个形参所指对象的字节大小,自动初始化size_t形参。
当类是某继承层次的一部分时,这是必需的。因为指针既可以指向基类对象,又可以指向派生类对象。
派生类的对象大小一般比基类对象要大,如果基类有virtual析构函数,则传给operator delete的大小,将根据被删除指针所指向对象的动态类型而变化。如果基类没有virtual析构函数,通过基类指针删除指向派生类对象的行为,是未定义的。
11、同样也可以定义成员operator new[]和operator delete[]来管理类类型的数组。如果这些函数存在,编译器就是用它们代替标准库的版本。
当类定义了自己的operator new和operator delete,标准库的operator new和operator delete就被屏蔽。
但是可以通过全局作用域操作符强制new或delete表达式使用全局的库函数。如:
T*p=::new T;
::delete p;
以下为自定义Vector类:
[cpp] view
plaincopy
#include<iostream>
using namespace std;
#include<algorithm>
#include<stdexcept>
template<typename T>
class Vector
{
public:
Vector()
{
first_free=NULL;
end=NULL;
element=NULL;
}
void push_back(T t)
{
if(first_free==end)
{
reallocate();
}
//alloc.construct(first_free,t);//调用构造函数。
new (first_free)T(t);
first_free++;
}
T &operator[](size_t s)
{
if(s>first_free-element)
{
throw out_of_range("下标越界啦!!");
}
else
{
return element[s];
}
}
const T &operator[](size_t s)const
{
if(s>first_free-element)
{
throw out_of_range("下标越界啦!!");
}
else
{
return element[s];
}
}
private:
void reallocate()
{
int num=first_free-element;
T*newelement;
if(num>0)
{
newelement=alloc.allocate(2*num);
//newelement=static_cast<T*>(operator new(2*num*sizeof(T)));//调用operator new申请空间。
uninitialized_copy(element,first_free,newelement);//将原来空间的元素复制到新地址。调用复制构造函数。
}
else
{
newelement=alloc.allocate(2);
//newelement=static_cast<T*>(operator new(2*sizeof(T)));//调用operator new申请空间。
}
for(T*i=element;i!=first_free;i++)
{
alloc.destroy(i);//调用析构函数。
//i->~T();//显式调用析构函数。
}
alloc.deallocate(element,end-element);//释放空间。
//operator delete (element);//使用operator delete释放空间。
//更新个指针。
element=newelement;
first_free=element+num;
if(num==0)
{
end=element+2;
}
else
{
end=element+2*num;
}
}
private:
allocator<T> alloc;//为什么此处加上static就会报链接错误呢。
T*newelement;
T* first_free;
T*end;
T*element;
};
int main(int argc,char**argv)
{
Vector<int> vi;
try
{
for(int i=0;i<20;i++)
vi.push_back(i+5);
for(int i=0;i<20;i++)
{
cout<<i+1<<": "<<vi[i]<<endl;
}
cout<<vi[28]<<endl;
}
catch (exception&e)
{
cout<<e.what()<<endl;
}
return 0;
}
12、接下来实现一个内存分配器基类。
1)它预先分配一块原始内存,来保存未构造的对象。创建新元素时,可以在预先分配的内存中构造,释放元素时将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这种策略常被成为维持一个自由列表。本例将自由列表实现为已分配但未构造的对象的链表。
2)有可能很多类都需要使用自由列表的分配策略。因此任何需要这种策略的类,都可以直接继承自这个类。因为这个类希望为任意类型服务,所以它被定义为类模板。
3)它有几个简单接口:
重写的operator new成员函数:new操作符被调用时调用此函数。它从自由列表中取走一个未构造的元素。因为new操作符的第二步会调用定位new操作符构造元素。Operator delete成员函数,它将要删除的指针指向的对象添加到自由列表中而不是返还给系统。
注意:此类只能用于包含在继承层次中的类型。因为它无法根据对象的实际类型,分配不同大小的对象。它的自由列表存储单一大小的对象。
4)使用allocator分配空间。它是静态的。自由列表头指针和其他一些变量都是被声明为static的成员变量,因为我们希望为所有相同类型的对象维持一个自由列表。
5)addToFreeList用于将一个指针指向的未构造或析构过的空间加入自由列表。next指针指向此类的派生类对象。因为自由列表存储的就是派生类对象的链表。
具体实现见代码:
#include<iostream>
#include<stdexcept>
using namespace std;
template<typename T>
class MemoryAllocate
{
public:
MemoryAllocate()
{
}
void *operator new(size_t s)
{
if(s!=sizeof(T))
throw runtime_error("传入的类型大小不符!");
if(!freeList)
{
T*array=alloc.allocate(num);
for(size_t i=0;i<num;i++)
{
addToFreeList(array+i);
}
}
T*p=freeList;
freeList=freeList->next;
return p;
}
void operator delete(void*s)
{
T*p=static_cast<T*>(s);
addToFreeList(p);
cout<<"wobeidiaoyongle "<<endl;//这句话被输出了,说明此函数被调用了。但是为何无法跟进呢???20120530 21:22
}
virtual ~MemoryAllocate()
{
}
private:
static void addToFreeList(T*t)
{
t->next=freeList;
freeList=t;
}
private:
static T*freeList;
static T*next;
static allocator<T> alloc;
static size_t num;
};
template<typename T> T*MemoryAllocate<T>::freeList=NULL;
template<typename T> size_t MemoryAllocate<T>::num=10;
template<typename T> T*MemoryAllocate<T>::next=NULL;
template<typename T> allocator<T> MemoryAllocate<T>::alloc;
class Derived:public MemoryAllocate<Derived>
{
public:
Derived()
{
i=0;
}
int i;
};
int main(int argc,char**argv)
{
try
{
Derived *mai=new Derived;
Derived *p=new Derived;
delete mai;
delete p;
}
catch (exception& e)
{
cout<<e.what()<<endl;
}
return 0;
}
相关文章推荐
- 浅谈C++多态性
- C++中属于整个类的的常量
- 离散数学:验证P,Q两个逻辑表达式是否逻辑等价(C语言实现)
- C++中cin、cin.get()、cin.getline()、getline()、gets()等函数的用法
- C\C++中的void 类型
- C++ Primer笔记(十七)多重继承与虚继承
- vector的成员函数解析
- 从零单排c++ primer(15)
- VS2012环境下C#调用C++生成的DLL
- 当析构函数遇到多线程──C++ 中线程安全的对象回调
- C++STL概述
- C语言指针转换为intptr_t类型
- C++之俄罗斯方块
- C++ 运算符重载
- C++中的单例模式
- C++的四种强制类型转换
- 简单的程序诠释C++ STL算法系列之十四:copy_backward
- 各种排序算法及c语言实现
- 阿姆斯特朗数 软件训练营 初级 入职前练习 C/C++
- C++基础