C++学习笔记二十-优化内存分配
一、C++ 中,内存分配和对象构造紧密纠缠,就像对象和内存回收一样。使用 new 表达式的时候,分配内存,并在该内存中构造一个对象:使用 delete 表达式的时候,调用析构函数撤销对象,并将对象所用内存返还给系统。
1.接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。
2.对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。
二、C++ 提供下面两种方法分配和释放未构造的原始内存:
1.allocator 类,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2.标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始的、未类型化的内存。
三、C++ 还提供不同的方法在原始内存中构造和撤销对象:
1.allocator 类定义了名为 construct 和 destroy 的成员,其操作正如它们的名字所指出的那样:construct 成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。
2.定位 new 表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。
3.可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。
4.算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy 算法一样执行,除了它们的目的地构造对象而不是给对象赋值之外。
四、allocator 类:allocator 类是一个模板,它提供类型化的内存分配以及对象构造与撤销。
1. 现代 C++ 程序一般应该使用 allocator 类来分配内存,它更安全更灵活。但是,在构造对象的时候,用 new 表达式比 allocator::construct 成员更灵活。有几种情况下必须使用 new。
2.deallocate 期待指向由 allocate 分配的空间的指针,传给 deallocate 一个零指针是不合法的。
3.allocate 成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换。类似地,deallocate 释放特定类型的内存,也不必转换为 void*。
五、operator new 函数和 operator delete 函数:
1. 当使用 new 表达式:
string * sp = new string("initialized"); // new expression
首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。
2.当使用 delete 表达式
delete sp;
删除动态分配对象的时候,发生两个步骤。首先,对 sp 指向的对象运行适当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。
3.new 表达式与 operator new 函数的区别:
a. 不能重定义 new 和 delete 表达式的行为。
b.new 表达式获通过调用 operator new 函数得内存,并接着在该内存中构造一个对象,通过执行 delete 表达式撤销一个对象,并接着调用 operator delete 函数,以释放该对象使用的内存。
4.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
5.定位 new 表达式:定位 new 表达式在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位 new 表达式使我们能够在特定的、预分配的内存地址构造一个对象。
定位 new 表达式的形式是:
new (place_address) type new (place_address) type (initializer-list)
六、显式析构函数的调用:
正如定位 new 表达式是使用 allocate 类的 construct 成员的低级选择,我们可以使用析构函数的显式调用作为调用 destroy 函数的低级选择。显式调用析构函数的效果是适当地清除对象本身。但是,并没有释放对象所占的内存,如果需要,可以重用该内存空间。
调用 operator delete 函数不会运行析构函数,它只释放指定的内存。
七、类特定的 new 和 delete:
1.默认情况下,new 表达式通过调用由标准库定义的 operator new 版本分配内存。通过定义自己的名为 operator new 和 operator delete 的成员,类可以管理用于自身类型的内存。
2.编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有 operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员 new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。
3.成员 new 和 delete 函数:如果类定义了这两个成员中的一个,它也应该定义另一个。
4.类成员 operator new 函数必须具有返回类型 void* 并接受 size_t 类型的形参。由 new 表达式用以字节计算的分配内存量初始化函数的 size_t 形参。
5.类成员 operator delete 函数必须具有返回类型 void。它可以定义为接受单个 void* 类型形参,也可以定义为接受两个形参,即 void* 和 size_t 类型。由 delete 表达式用被 delete 的指针初始化 void* 形参,该指针可以是空指针。如果提供了 size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化 size_t 形参。
除非类是某继承层次的一部分,否则形参 size_t 不是必需的。当 delete 指向继承层次中类型的指针时,指针可以指向基类对象,也可以指向派生类对象。派生类对象的大小一般比基类对象大。如果基类有 virtual 析构函数,则传给 operator delete 的大小将根据被删除指针所指对象的动态类型而变化;如果基类没有 virtual 析构函数,那么,通过基类指针删除指向派生类对象的指针的行为,跟往常一样是未定义的。
6.这些函数隐式地为静态函数,不必显式地将它们声明为 static,虽然这样做是合法的。成员 new 和 delete 函数必须是静态的,因为它们要么在构造对象之前使用(operator new),要么在撤销对象之后使用(operator delete),因此,这些函数没有成员数据可操纵。像任意其他静态成员函数一样,new 和 delete 只能直接访问所属类的静态成员。
八、数组操作符 new[] 和操作符 delete[]
1.也可以定义成员 operator new[] 和 operator delete[] 来管理类类型的数组。如果这些 operator 函数存在,编译器就使用它们代替全局版本。
2.类成员 operator new[] 必须具有返回类型 void*,并且接受的第一个形参类型为 size_t。用表示存储特定类型给定数目元素的数组的字节数值自动初始化操作符的 size_t 形参。
3.成员操作符 operator delete[] 必须具有返回类型 void,并且第一个形参为 void* 类型。用表示数组存储起始位置的值自动初始化操作符的 void* 形参。
4.类的操作符 delete[] 也可以有两个形参,第二个形参为 size_t。如果提供了附加形参,由编译器用数组所需存储量的字节数自动初始化这个形参。
九、覆盖类特定的内存分配
1.如果类定义了自己的成员 new 和 delete,类的用户就可以通过使用全局作用域确定操作符,强制 new 或 delete 表达式使用全局的库函数。如果用户编写
Type *p = ::new Type; // uses global operator new ::delete p; // uses global operator delete
2.那么,即使类定义了自己的类特定的 operator new,也调用全局的 operator new;delete 类似。
3.如果用 new 表达式调用全局 operator new 函数分配内存,则 delete 表达式也应该调用全局 operator delete 函数。