第十八章

本文详细介绍了C++中的内存管理技术,包括allocator类的使用、new与delete表达式的运作原理、定位new表达式的应用及成员new和delete函数的定义。此外,还探讨了通过RTTI实现的运行时类型识别方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

18.1.1

        对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。

18.1.2
allocator<T> a;定义名为 a 的 allocator 对象,可以分配内存或构造 T 类型的对象
a.allocate(n)分配原始的未构造内存以保存 T 类型的 n 个对象
a.deallocate(p,n)释放内存,在名为 p 的 T* 指针中包含的地址处保存 T 类 型的 n 个对象。运行调用 deallocate 之前在该内存中构 造的任意对象的 destroy 是用户的责任
a.construct(p,t)在 T* 指针 p 所指内存中构造一个新元素。运行 T 类型的 复制构造函数用 t 初始化该对象
a.destroy(p)运行 T* 指针 p 所指对象的析构函数

        allocator类将内存分配和对象构造分开。当 allocator对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的, allocator的用户必须分别construct和destroy放置在该内存中的对象。

18.1.3

        当使用 new表达式

string * sp = new string("initialized");

        实际上发生三个步骤。首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。

        当使用 delete 表达式

delete sp;

        发生两个步骤。首先,对 sp 指向的对象运行适当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。

        operator new 和 operator delete 函数有两个重载版本,每个版本支持相关的new 表达式和 delete 表达式:

void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an array
T* newelements = alloc.allocate(newcapacity);
T* newelements = static_cast<T*>(operator new[](newcapacity * sizeof(T)));

        一般而言,使用 allocator 比直接使用 operator new和 operator delete 函数更为类型安全allocate 成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换。

18.1.4

        定位 new表达式在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。定位 new 表达式的形式是: 

new (place_address) type
new (place_address) type (initializer-list)

        其中 place_address 必须是一个指针,而 initializer-list 提供了(可能为空的)初始化列表,以便在构造新分配的对象时使用。

alloc.construct (first_free, t);
new (first_free) T(t);
        定位 new 表达式比 allocator 类的 construct 成员更灵活。定位 new 表达式初始化一个对象的时候,它可以使用任何构造函数,并直接建立对象。construct 函数总是使用复制构造函数 
allocator<string> alloc;
string *sp = alloc.allocate(2); // allocate space to hold 2 strings
// two ways to construct a string from a pair of iterators
new (sp) string(b, e); // construct directly in place
alloc.construct(sp + 1, string(b, e)); // build and copy a temporary

        定位 new 表达式使用了接受一对迭代器的 string 构造函数,在 sp 指向的空间直接构造 string 对象。当调用 construct 函数的时候,必须首先从迭代器构造一个 string 对象,以获得传递给 construct 的 string 对象,然后,该函数使用 string 的复制构造函数, 将那个未命名的临时 string 对象复制到sp 指向的对象中对某些类而言,使用复制构造函数是不可能的(因为复制构造函数是私有的),或者是应该避免的,在这种情况下,也许有必要使用定位 new 表达式。

18.1.5

        定位 new 表达式是使用 allocate 类的 construct 成员的低级选择,我们可以使用析构函数的显式调用作为调用 destroy 函数的低级选择:

for (T *p = first_free; p != elements; /* empty */ )
alloc.destroy(--p);
for (T *p = first_free; p != elements; /* empty */ )
p->~T(); // call the destructor

        显式调用析构函数的效果是适当地清除对象本身。但是,并没有释放对象所占的内存,如果需要,可以重用该内存空间。

18.1.6

        编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

        优化 new 和 delete 的行为的时候,只需要定义 operator new 和operator delete 的新版本,new 和 delete 表达式自己照管对象的构造和撤销。

        operator new 函数必须具有返回类型 void* 并接受 size_t 类型的形参。由 new 表达式用以字节计算的分配内存量初始化函数的 size_t 形参。operator delete 函数必须具有返回类型 void。它可以定义为接受单个 void* 类型形参, 也可以定义为接受两个形参, 即 void* 和 size_t 类型。由 delete 表达式用被 delete 的指针初始化 void* 形参,该指针可以是空指针。如果提供了 size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化 size_t 形参除非类是某继承层次的一部分,否则形参 size_t 不是必需的如果基类有 virtual 析构函数(第15.4.4 节),则传给 operator delete 的大小将根据被删除指针所指对象的动态类型而变化;如果基类没有 virtual 析构函数,那么,通过基类指针删除指向派生类对象的指针的行为,跟往常一样是未定义的。

        成员 new 和 delete 函数必须是静态的,因为它们要么在构造对象之前使用(operator new),要么在撤销对象之后使用(operator delete),因此,这些函数没有成员数据可操纵。像任意其他静态成员函数一样,new 和 delete 只能直接访问所属类的静态成员。

18.2

        通过下面两个操作符提供通过运行时类型识别(RTTI)
  • typeid 操作符,返回指针或引用所指对象的实际类型
  • dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用

        对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译时计算 RTTI 操作符。

        可以使用 dynamic_cast 操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。 dynamic_cast 涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型(或其派生类型)的对象,则 dynamic_cast 失败。如果转换到指针类型的 dynamic_cast 失败,则 dynamic_cast 的结果是 0 值;如果转换到引用类型的 dynamic_cast 失败,则抛出一个 bad_cast 类型的异常

        因此,dynamic_cast 操作符一次执行两个操作。它首先验证被请求的转换是否有效,只有转换有效,操作符才实际进行转换

18.2.2

        如果操作数不是类类型(例如指针)或者是没有虚函数的类,则 typeid 操作符指出操作数的静态类型;如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型

        只有当 typeid 的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针(相对于指针指向的对象)返回指针的静态的、编译时类型

        如果指针 p 的值是 0,那么,如果 p 的类型是带虚函数的类型,则typeid(*p) 抛出一个 bad_typeid 异常;如果 p 的类型没有定义任何虚函数,则结果与 p 的值是不相关的。正像计算表达式 sizeof(第 5.8 节)一样,编译器不计算 *p,它使用 p 的静态类型,这并不要求 p 本身是有效指针

18.2.4

        默认构造函数和复制构造函数以及赋值操作符都定义为 private,所以不能定义或复制 type_info 类型的对象。程序中创建 type_info 对象的唯一方法是使用 typeid 操作符

        name 函数为 type_info 对象所表示的类型的名字返回 C 风格字符串:

int iobj;
cout << typeid(iobj).name() << endl         //i
<< typeid(8.16).name() << endl              //d
<< typeid(std::string).name() << endl       //Ss
<< typeid(Base).name() << endl              //4Base
<< typeid(Derived).name() << endl;          //7Derived

18.3

        成员指针包含类的类型以及成员的类型。成员指针只应用于类的非 static 成员。static 类成员不是任何对象的组成部分,所以不需要特殊语法来指向 static 成员,static 成员指针是普通指针。

class Screen {
public:
typedef std::string::size_type index;
char get() const;
char get(index ht, index wd) const;
private:
std::string contents;
index cursor;
index height, width;
};
数据成员指针

        Screen 类的 contents 成员的类型为 std::string。contents 的完全类型是“Screen 类的成员, 其类型是 std::string”。 因此, 可以指向 contents 的指针的完全类型是“指向 std::string 类型的 Screen 类成员的指针”,这个类型可写为:

string Screen::*ps_Screen;
函数成员指针

        成员函数的指针必须在三个方面与它所指函数的类型相匹配:

  • 函数形参的类型和数目,包括成员是否为 const。
  • 返回类型。
  • 所属类的类型。

        这个类型指定 Screen 类的 const 成员函数的指针,不接受形参并返回char 类型的值。这个 get 函数版本的指针可以像下面这样定义和初始化:

// pmf points to the Screen get member that takes no arguments
char (Screen::*pmf)() const = &Screen::get;
为成员指针使用类型别名

        类型别名可以使成员指针更容易阅读。例如,下面的类型别名将 Action 定义为带两个形参的 get 函数版本的类型的另一名字:

// Action is a type name
typedef char (Screen::*Action)(Screen::index, Screen::index) const;
实用类指针成员

        .* 和 ->*是两个新的操作符,它们使我们能够将成员指针绑定到实际对象。这两个操作符的左操作数必须是类类型的对象类类型的指针右操作数是该类型的成员指针

  • 成员指针解引用操作符(.*)从对象或引用获取成员。
  • 成员指针箭头操作符(->*)通过对象的指针获取成员。
// pmf points to the Screen get member that takes no arguments
char (Screen::*pmf)() const = &Screen::get;
Screen myScreen;
char c1 = myScreen.get(); // call get on myScreen
char c2 = (myScreen.*pmf)(); // equivalent call to get
Screen *pScreen = &myScreen;
c1 = pScreen->get(); // call get on object to which pScreen
points
c2 = (pScreen->*pmf)(); // equivalent call to get

        相同的成员指针操作符用于访问数据成员

Screen::index Screen::*pindex = &Screen::width;
Screen myScreen;
// equivalent ways to fetch width member of myScreen
Screen::index ind1 = myScreen.width; // directly
Screen::index ind2 = myScreen.*pindex; // dereference to get width
Screen *pScreen;
// equivalent ways to fetch width member of *pScreen
ind1 = pScreen->width; // directly
ind2 = pScreen->*pindex; // dereference pindex to get width

18.4

        可以在另一个类内部定义一个类,这样的类是嵌套类,也称为嵌套类型。嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是互相独立的。嵌套类型的对象不具备外围类所定义的成员,同样,外围类的成员也不具备嵌套类所定义的成员。

        外围类对嵌套类的成员没有特殊访问权,并且嵌套类对其外围类的成员也没有特殊访问权

        嵌套类定义了其外围类中的一个类型成员。像任何其他成员一样,外围类决定对这个类型的访问(public、protect、private成员)。

        在其类外部定义的嵌套类成员,必须定义在定义外围类的同一作用域中。在其类外部定义的嵌套类的成员,不能定义在外围类内部,嵌套类的成员不是外围类的成员

template <class Type> Queue<Type>::QueueItem::QueueItem(const Type &t): item(t), next(0) { }

/*    哇!!!终于写完了这个系列,真是不容易啊    */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值