概述
优化内存分配
c++提供两种方法分配和释放未构造的原始内存:
1.allocator类
2.标准库中的new和delete操作符
应当引起注意的是,这里的内存分配不是分配内存给对象的实体,而是分配一个未使用的空间,此后在这片空间上的操作,都由allocator类给出,这样做实际上实现了内存空间分配与对象构建的分离。
使用步骤:
由于allocator将内存空间的分配和对象的构建分离,故使用allocator分为以下几步
1.allocator与类绑定,因为allocator是一个泛型类
2.allocate()申请指定大小空间
3.construct()构建对象,其参数为可变参数,所以可以选择匹配的构造函数
4.使用,与其它指针使用无异
5.destroy()析构对象,此时空间还是可以使用
6.deallocate()回收空间
allocator<T> a;
a.allocate(n);//分配原始未构造内存保存n个T对象
a.deallocate(p,n);//释放内存,在名为p的T*指针中包含的地址处保存T类型的n个对象
a.construct(p,t);//在T*指针p所指内存中构造一个新元素,使用复制构造函数用t初始化该对象
a.destory(p);//运行T*指针p所指对象的析构函数
new函数与delete函数
当new函数运行时,实际上发生三个步骤:
1.调用operator new的标准库函数,分配足够大的原始未类型化的内存,以保存指定类型的一个对象。
2.运行该类型的一个构造函数,使用指定初始化式构造对象
3.返回指向新分配并构造对象的指针
当delete函数运行时,实际上发生两个步骤:
1.对指针指向的对象调用适当的析构函数
2.调用operator delete的标准库函数释放对象使用的内存
定位new表达式
标准库函数operator new和operator delete是allocator的allocate和deallocate成员的低级版本,他们都分配但不初始化内存。
allocator的成员construct和destroy也有两个低级选择,这些成员在由allocator对象分配的空间中初始化和撤销对象。
类似construct成员,有第三种new表达式,称为定位new
定位new不分配内存,接受指向已分配但未构造内存的指针,在该内存中初始化一个对象。它能让我们在特定的,预分配的内存地址构造一个对象。
示例:
new (ptr) string(str); //在指针ptr空间内构造对象string,初始化为str的复制构造
显式析构函数的调用
析构函数的显式调用是destroy函数的低级版本
它只清除对象本身,而不释放其所占的内存。
而operator delete函数不会运行析构函数,它只释放指定的内存。
属于类的new与delete
编译器看到类类型的new或delete表达式时,它查看该类是否有operator new或operator delete成员,如果类定义了自己的成员new和delete函数,则使用那些函数为对象分配和释放内存,否则调用这些函数的标准库版本。
对于成员new和delete函数,如果类定义了二者其中的一个,则它有义务定义另一个。
成员new和delete
·类成员operator new函数必须具有返回类型void*(任意类型的指针)并接受size_t类型的形参。
·类成员operator delete函数必须具有返回类型void,接受void*和size_t类型的两个形参。
·一般来说,除非类是某继承层次的一部分,否则形参size_t不是必须的。
·当delete指向继承层次中类型的指针时,指针可以指向基类对象,也可以指向派生类对象,这意味着它必须支持virtual的基类析构函数调用派生类的析构函数。
·new和delete函数隐式的为静态函数,不必显式地将其声明为static(虽然合法)。它们必须是静态函数的原因是:它们的使用总是在对象构造之前,或对象撤销之后,因此这些函数没有成员数据可以操纵,只能直接访问所属类的静态成员。
覆盖类特定的内存分配
string *p=: :new string;
: :delete p;
这样不管该类是否声明了new和delete函数,它们都调用全局函数operator new与operator delete。
RTTI(运行时类型识别)
c++通过下面两种方式实现RTTI:
1.typeid操作符,返回指针或引用所指对象的实际类型。
2.dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类型的指针或引用。
对于带虚函数的类,在运行时执行RTTI操作符,但对于其他类型,在编译时执行RTTI操作符。
dynamic_cast操作符
可以使用该操作符将基类对象的引用或指针转换为同一继承层次中其他类型的引用或指针。
若指针转换失败,则dynamic_cast的结果为0;
若引用转换失败,则抛出一个bad_cast类型的异常。
一般来说,引用或指针绑定的对象的类型在编译时是未知的,基类的指针可以赋值为指向派生类对象,同样,基类的引用也可以用派生类对象初始化,因此,dynamic_cast操作符执行的验证必须在运行时进行。
使用示例:
1.指针
if(Derived *derivedPtr=dynamic_cast<Derived*>(basePtr)) //将指向基类的指针在运行时转为派生类
{
//这里该指针操作的是派生类对象
}
else
{
//这里该指针操作的是基类对象
}
2.引用
void f(const Base &b)
{
try{
const Derived &d=dynamic_cast<const Derived&>(b);
//b是基类类型的对象,将其转换成了想要的派生类对象的引用
} catch(bad_cast)
{
//处理异常
}
}
typeid操作符
typeid将返回该类的类型
使用示例:
Based *bp;
Derived *dp;
if(typeid(*bp)==typeid(*dp))
{
}
else if(typeid(*bp)==typeid(Based))
{
}
typeid神奇的机制是:
当使用基类指针指向子类成员时,如果该基类中有虚函数,则运行时检测typeid将会返回具体的派生类类型;若基类中不含有虚函数,则运行时typeid将会返回基类类型。
常见于各类定义的equal函数或重载的==运算符函数中,这样带来的好处是,不必对每一个类都写一个比较类型的函数。
如若要显式打印出对象的具体类型,可用type_info类的name()函数。
例如:
cout<<typeid(*ptr).name()<<endl;
类成员的指针
成员指针只应用于类的非static成员,由于static成员不是任何对象的组成部分,所以不需要特殊的语法来指向static成员,static成员指针是普通指针。
示例:
class A
{
private:
string str;
int num;
public:
const char f();
const char g(int a,string b);
}
string A: : *ptr; //指向A类下string成员的指针
int A: : *a; //指向A类下int成员的指针
指向成员函数的指针
它需要指明下面的信息
1.函数形参的类型和数目,包括成员是否为const
2.返回类型
3.所属类的类型
示例:
char (A: : *ptr) ( ) const
char (A: : *ptr) ( ) const =&A: :f ;
char (A: : *ptr) ( int ,string ) const =&A: :f ;
const_cast
有时一个成员函数在逻辑上是const,在该函数中不应修改任何成员值,但有时会需要初始赋值,此时由于函数被声明为const限定,程序不允许其修改该类的成员值,导致问题。
解决方法:
使用const_cast强制将该const函数指针赋给一个非const函数指针,使用该函数指针完成需要做的操作。
最后
以上就是如意酸奶为你收集整理的c++ primer 随笔(7) 特殊工具与技术的全部内容,希望文章能够帮你解决c++ primer 随笔(7) 特殊工具与技术所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复