网站建设合同怎么写,wordpress如何设置404页面,做国际网站需要多少钱,微信小程序怎么做团购条款 49#xff1a;了解 new-handler 的行为
当operator new无法满足某一内存分配需求时#xff0c;会不断调用一个客户指定的错误处理函数#xff0c;即所谓的 new-handler#xff0c;直到找到足够内存为止 new-handler 是一个 typedef#xff0c;指向一个无参数值无返回…条款 49了解 new-handler 的行为
当operator new无法满足某一内存分配需求时会不断调用一个客户指定的错误处理函数即所谓的 new-handler直到找到足够内存为止 new-handler 是一个 typedef指向一个无参数值无返回值的函数。可以通过 set_new_handler 函数去指定客户想要的 new-handler。 set_new_handler 函数接受一个新的 new-handler 参数返回被替换掉的 new-handler 函数
namespace std {using new_handler void(*)();new_handler set_new_handler(new_handler) noexcept; // 返回值为原来持有的 new-handler
}设计良好的 new-handler 函数
让更多的内存可被使用 可以让程序一开始执行就分配一大块内存而后当 new-handler 第一次被调用将它们释还给程序使用造成operator new的下一次内存分配动作可能成功。安装另一个 new-handler如果当前的new-handler不能够为你提供更多的内存可能另外一个new-handler可以。即在当前的new-handler的位置上安装另外一个new-handler通过调用set_new_handler。下次operator new调用new-handler函数的时候它会调用最近安装的。这需要让new_handler修改会影响new-handler行为的static数据,命名空间数据或者全局数据卸除 new-handler 将nullptr传给set_new_handler使operator new在内存分配不成功时抛出异常抛出 bad_alloc或派生自 bad_alloc的异常 这样的异常不会被operator new捕捉会被传播到内存分配处不返回 通常调用std::abort或std::exit
以不同的方式处理内存分配的情况比如按不同的 class 进行处理。c 并不支持为每一个 class 提供专属版本的 new_handler要用静态成员
public:static std::new_handler set_new_handler(std::new_handler p) noexcept;static void* operator new(std::size_t size);
private:static std::new_handler currentHandler;
};
// 做和 std::set_new_handler 相同的事情
std::new_handler Widget::set_new_handler(std::new_handler p) noexcept {std::new_handler oldHandler currentHandler;currentHandler p;return oldHandler;
}
void* Widget::operator new(std::size_t size) {auto globalHandler std::set_new_handler(currentHandler); // 切换至 Widget 的专属 new-handlervoid* ptr ::operator new(size); // 分配内存或抛出异常std::set_new_handler(globalHandler); // 切换回全局的 new-handlerreturn globalHandler;
}
std::new_handler Widget::currentHandler nullptr;以对象管理资源的方法
class NewHandlerHolder {
public:explicit NewHandlerHolder(std::new_handler nh): handler(nh) {}
~NewHandlerHolder() {std::set_new_handler(handler);}
private:std::new_handler handler;
};Widget::operator new的实现可改为
void* Widget::operator new(std::size_t size) noexcept{NewHandlerHolder h(std::set_new_handler(currentHandler));return ::operator new(size);
}Widget的客户调用 :
void OutOfMem();
Widget::set_new_handler(OutOfMem);
auto pw1 new Widget; // 若分配失败则调用 OutOfMem
Widget::set_new_handler(nullptr);
auto pw2 new Widget; // 若分配失败则抛出异常上述代码每个class要实现自己的set_new_handler和operator new。可以用template。建立起一个“mixin”风格的基类让其派生类继承它们所需的set_new_handler和operator new并且使用模板确保每一个派生类获得一个实体互异的currentHandler成员变量
templatetypename T
class NewHandlerSupport { // “mixin”风格的基类
public:static std::new_handler set_new_handler(std::new_handler p) noexcept;static void* operator new(std::size_t size);... // 其它的 operator new 版本见条款 52
private:static std::new_handler currentHandler;
};templatetypename T
std::new_handler NewHandlerSupportT::set_new_handler(std::new_handler p) noexcept {std::new_handler oldHandler currentHandler;currentHandler p;return oldHandler;
}templatetypename T
void* NewHandlerSupportT::operator new(std::size_t size) {auto globalHandler std::set_new_handler(currentHandler);void* ptr ::operator new(size);std::set_new_handler(globalHandler);return globalHandler;
}templatetypename T
std::new_handler NewHandlerSupportT::currentHandler nullptr;class Widget : public NewHandlerSupportWidget {
public:... // 不必再声明 set_new_handler 和 operator new
};此处的模板参数T并没有真正被当成类型使用而仅仅是用来区分不同的派生类使得模板机制为每个派生类具现化出一份对应的currentHandler 即 CRTPcurious recurring template pattern奇异递归模板模式也被用于实现静态多态
template class Derived
struct Base {void Interface() {static_castDerived*(this)-Implementation(); // 在基类中暴露接口}
};
struct Derived : BaseDerived {void Implementation(); // 在派生类中提供实现
};C 保留了传统的“分配失败便返回空指针”的operator new称为 nothrow new通过std::nothrow对象来使用
Widget* pw1 new Widget; // 如果分配失败抛出 bad_alloc
if (pw1 nullptr) ... // 这个测试一定失败
Widget* pw2 new (std::nothrow) Widget; // 如果分配失败返回空指针
if (pw2 nullptr) ... // 这个测试可能成功条款50了解new和delete的合理替换时机
定制operator new和operator delete的理由 用来检测运行上的错误如果将“new 所得内存”delete 掉却不幸失败会导致内存泄漏如果在“new 所得内存”身上多次 delete 则会导致未定义行为。如果令operator new持有一串动态分配所得地址而operator delete将地址从中移除就很容易检测出上述错误用法 另外自定义new分配超额内存在额外空间放置特定签名/byte pattern。在delete时检查是否不变反之肯定存在“overruns”写入点在分配区块尾部之后或“unferruns”写入点在分配区块头部之前delete也可log那个指针。例如
static const int signature 0xDEADBEEF; // 调试“魔数”
using Byte unsigned char;void* operator new(std::size_t size) {using namespace std;size_t realSize size 2 * sizeof(int); // 分配额外空间以塞入两个签名void* pMem malloc(realSize); // 调用 malloc 取得内存if (!pMem) throw bad_alloc();// 将签名写入内存的起点和尾端*(static_castint*(pMem)) signature;*(reinterpret_castint*(static_castByte*(pMem) realSize - sizeof(int))) signature;return static_castByte*(pMem) sizeof(int); // 返回指针指向第一个签名后的内存位置
}这段代码不保证内存对齐许多地方不遵守c规范见条款51 收集使用上的统计数据 定制 new 和 delete 动态内存的相关信息分配区块的大小分布寿命分布FIFO先进先出、LIFO后进先出或随机次序的倾向性不同的分配/归还形态使用的最大动态分配量等等。 增加分配和归还的速度泛用型分配器通常比定制分配器慢。类专属的分配器可以做到“区块尺寸固定”例如 Boost 提供的 Pool 程序库。又例如编译器所带的内存管理器是线程安全的但如果你的程序是单线程的你也可以考虑写一个不线程安全的分配器来提高速度 降低缺省内存管理器带来的空间额外开销泛用型分配器通常比定制分配器使用更多内存。因为常常在每一个分配区块身上招引某些额外开销。针对小型对象而开发的分配器例如 Boost 的 Pool 程序库本质上消除了这样的额外开销 弥补缺省分配器中的非最佳内存对齐许多计算机体系架构要求特定的类型必须放在特定的内存地址上如果没有奉行这个约束条件可能导致运行期硬件异常或者访问速度变低。std::max_align_t用来返回当前平台的最大默认内存对齐类型对于malloc分配的内存其对齐和max_align_t类型的对齐大小应当是一致的但若对malloc返回的指针进行偏移就没有办法保证内存对齐 C11 中内存对齐相关方法 将相关对象成簇集中如果知道特定的某个数据结构往往被一起使用又希望在处理这些数据时将“内存页错误page faults”的频率降至最低可以考虑为此数据结构创建一个堆将它们成簇集中在尽可能少的内存页上。一般可以使用 placement new 达成这个目标条款52 获得非传统的行为如分配和归还共享内存这些事情只能被 C API 完成则可以将 C API 封在 C 的外壳里写在定制的 new 和 delete 中
条款 51编写 new 和 delete 时需固守常规
编写自己的new存不足时必须不断调用 new-handler如果无法供应客户申请的内存就抛出std::bad_alloc异常。即使客户需求为0字节operator new也得返回一个合法的指针
void* operator new(std::size_t size) {using namespace std;if (size 0) // 处理0字节申请size 1; // 将其视为1字节申请while (true) {if (...) // 如果分配成功return ...; // 返回指针指向分配得到的内存// 如果分配失败调用目前的 new-handlerauto globalHandler get_new_handler(); // since C11if (globalHandler) (*globalHandler)();else throw std::bad_alloc();}
}如果子类未声明自己的operator new会从父类继承过来使的子类使用了父类new分配方式。但子类与父类的大小多数时候是不同的因此成员函数版本
void* Base::operator new(std::size_t size) {if (size ! sizeof(Base))return ::operator new(size); // 转交给标准的 operator new 进行处理...
}此时无需检测大小是否为0因为类必须有非零大小条款39 如果要实现operator new[]即array new唯一要做的就是分配一块未加工的原始内存。因为无法对尚未存在的元素对象做任何事甚至无法计算含有多少个对象 编写自己的delete删除空指针永远安全
void operator delete(void* rawMemory) noexcept {if (rawMemory 0) return;// 归还 rawMemory 所指的内存
}成员函数版本
void Base::operator delete(void* rawMemory, std::size_t size) noexcept {if (rawMemory 0) return;if (size ! sizeof(Base)) {::operator delete(rawMemory); // 转交给标准的 operator delete 进行处理return;}// 归还 rawMemory 所指的内存
}条款 52写了 placement new 也要写 placement delete
placement new如果你的new接收的参数除了必定有的size_t外还有其他
void* operator new(std::size_t, std::ostream logStream);
auto pw new (std::cerr) Widget;elete同理 当创建对象时会先进行new函数然后调用构造函数如果构造出现异常就需要delete否则内存泄漏但客户手上的指针仍未指向该被归还的内存因此由c系统本身调用delete。系统需要知道哪个delete该被调用 当抛出异常时运行期系统会寻找参数个数和类型都与 operator new 相同的某个 operator delete。 placement delete 只有在 placement new 的调用构造函数异常时才会被系统调用即使对一个用 placement new 申请出的指针使用 delete也绝不会调用 placement delete。因此如果要处理 placement new 相关的内存泄漏问题我们必须同时提供一个正常版本的 delete 和 placement 版本的 delete。前者用于构造期间无异常抛出后者用于构造期间有异常抛出
class Widget {
public:static void* operator new(std::size_t size, std::ostream logStream); // placement newstatic void operator delete(void* pMemory); // delete 时调用的正常 operator deletestatic void operator delete(void* pMemory, std::ostream logStream); // placement delete
};还要注意同名函数遮掩调用的问题
class Base {
public:static void* operator new(std::size_t size, std::ostream logStream);...
};
auto pb new Base; // 无法通过编译
auto pb new (std::cerr) Base; // 正确同理子类的operator new会遮掩global和父类继承的operator new版本
class Derived : public Base {
public:static void* operator new(std::size_t size);...
};auto pd new (std::clog) Derived; // 无法通过编译
auto pd new Derived; // 正确除非目的就是禁用否则要确保这些默认形式对定制类型依然可用
void* operator(std::size_t) throw(std::bad_alloc); // normal new
void* operator(std::size_t, void*) noexcept; // placement new
void* operator(std::size_t, const std::nothrow_t) noexcept; // nothrow new可以准备一个基类包含所有的正常版本new和delete
class StadardNewDeleteForms{
public:// normal new/deletestatic void* operator new(std::size_t size){return ::operator new(size);}static void operator delete(void* pMemory) noexcept {::operator delete(pMemory);}// placement new/deletestatic void* operator new(std::size_t size, void* ptr) {return ::operator new(size, ptr);}static void operator delete(void* pMemory, void* ptr) noexcept {::operator delete(pMemory, ptr);}// nothrow new/deletestatic void* operator new(std::size_t size, const std::nothrow_t nt) {return ::operator new(size,nt);}static void operator delete(void* pMemory,const std::nothrow_t) noexcept {::operator delete(pMemory);}
};凡是需要自定义的class可以继承该类并使用using声明式条款33