zoukankan      html  css  js  c++  java
  • 控制内存分配

    某些应用程序对内存分配有特殊需求,因此不能将标准内存管理机制直接应用在这些程序。因此需要自定义内存分配的细节,比如使用关键字 new 将对象放置在特定的内存空间中。

    重载 new 和 delete

    当使用 new 表达式:

    string *sp = new string("a value");		//@ 分配并初始化一个 string 对象
    string *arr = new string[10];		    //@ 分配10个默认初始化的 string 对象
    

    实际上执行了三步:

    • new 表达式调用 operator new 或者 operator new[] 的标准库函数,该函数分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象或者对象的数组。
    • 编译器运行相应的构造函数以构造这些对象,并为其传入初始值。
    • 对象被分配了空间并构造完成,返回一个指向该对象的指针。

    当使用一条 delete 表达式删除一个动态分配的对象时:

    delete sp;		//@ 销毁*sp,然后释放视频指向的内存空间
    delete[] arr;	//@ 销毁数组中的元素,然后释放对应的内存空间
    

    实际上执行了两步:

    • 对 sp 所指的对象或者 arr 所指的数组中的元素执行对应的析构函数。
    • 编译器调用名为 operator delete 或者 operator delete[] 的标准库函数释放内存空间。

    如果应用程序希望控制内存分配过程,则需要自定义 operator new 函数和 operator delete 函数。即使标准库中已经存在这两个函数的定义,仍旧可以定义自己的版本,编译器不会对这种重复定义提出异议,相反,编译器将使用自定义的版本替换标准库的版本。

    当自定义了全局的 operator new 和 operator delete 函数后,我们就担负起了控制动态内存分配的职责,这两个函数必须是正确的:它们是程序整个处理过程中至关重要的一部分。

    应用程序可以定义全局的 operator new 和 operator delete 函数,也可以将它们定义为成员函数。当编译器发现 new 或者 delete 表达式后,将在程序中查找可调用的 operator 函数。

    • 如果被分配或者释放的是类类型,则编译器首先在类的及其基类的作用域中查找,若有,则调用相应的成员。
    • 如果在上面的查找过程中没有找到,编译器将在全局作用域查找匹配的函数,如果找到,则调用相应的版本。
    • 如果上面的查找过程没有找到,则使用标准库自定义的版本。

    可以使用作用域运算符令 new 或者 delete 表达式忽略定义在类中的函数,直接执行全局作用域中的版本:

    ::operator new
    ::operator delete
    

    operator new 和 operator delete 接口

    标准库定义了 operator new 函数和 operator delete 函数的8个重载版本。前四个版本可能抛出 bad_alloc 异常,后四个版本则不会抛出异常:

    //@ 这些版本可能抛出异常
    void* operator new(size_t);					//@ 分配一个对象
    void* operator new[](size_t);				//@ 分配一个数组
    void operator delete(void*) noexcept;		//@ 释放一个对象
    void operator delete[](void*)noexcept;		//@ 释放一个数组
    
    //@ 这些版本承诺不会抛出异常
    void* operator new(size_t,nothrow_t&);					//@ 分配一个对象
    void* operator new[](size_t,nothrow_t&);				//@ 分配一个数组
    void operator delete(void*,nothrow_t&) noexcept;		//@ 释放一个对象
    void operator delete[](void*,nothrow_t&)noexcept;		//@ 释放一个数组
    
    • nothrow_t 是定义 new 头文件中的一个 struct,在这个类型中不包含任何成员。
    • new 头文件还定义了一个名为 nothrow 的 const 对象,用户可以通过这个对象请求 new 的非抛出版本。
    • 与析构函数类似,operator delete 也不允许抛出异常。
    • 当重载这些运算符时,必须使用 noexcept 异常说明符,指定其不抛出异常。

    应用程序可以自定义上面函数版本中的任意一个,前提是自定义的版本必须位于全局作用域或者类作用域中。

    当将上面的运算符函数定义成类的成员时,它们是隐式静态的,无须声明 static 关键字,当然显式声明也不会报错。因为 operator new 是在对象构造之前,operator delete 是在对象销毁之后,所以这两个函数必须是静态的,并且它们不能操纵类的任何数据成员。

    operator new 和 operator new []

    • 返回类型必须是 void* 。
    • 第一个参数必须是 size_t 类型,并且不能有默认实参。
    • 为单个对象分配空间时使用 operator new,为数组分配空间时使用 operator new[]。
    • 编译器调用 operator new 时,把存储指定类型对象所需的字节数传给 size_t 形参;当调用 operator new[] 时,传入的则是存储数组中所有元素所需的空间。
    • 如果想要自定义 operator new 函数,则可以为它提供额外的形参,此时用到这些自定义函数的 new 表达式必须使用 new 的定位形式,将实参传递给形参。
    • void * operator new(size_t,void*) 是标准库版本,用户不能重新定义。

    operator delete 和 operator delete[]

    • 返回类型必须是 void。
    • 第一个形参必须是 void*.
    • 执行一条 delete 表达式将调用相应的 operator 函数,并用指向待释放内存的指针来初始化 void* 形参。
    • 如果将 operator delete 或 operator delete[] 定义成类的成员,该函数可以包含另一个 size_t 参数,此时形参的初始值是第一个形参所指对象的字节数。size_t 可用于删除继承体系中的对象,如果基类有一个虚析构函数,则传递给 operator delete 的字节数因待删除指针所指对象的动态类型不同而有所区别。实际上运行的 operator delete 函数版本也由对象的动态类型决定。

    new/delete 表达式与 operator new/opeator delete 函数

    operator new 函数和 operator delete 函数的目的在于改变内存分配方式,但是无论如何都不能改变 new 运算符和 delete 运算符的基本含义。

    malloc 函数与 free 函数

    malloc 接受一个表示待分配字节数 size_t,返回指向分配空间的指针或者返回0表示分配失败。

    free 接受一个 void*,它是 malloc 返回的指针的副本,free 将相关内存返回给系统。调用 free(0) 没有任何意义。

    如下所示编写 operator new 和 operator delete 的一种简单形式,其它版本与之类似:

    void* operator new(size_t size)
    {
    	if (void* mem = malloc(size))
    		return mem;
    	else
    		throw bad_alloc();
    }
    
    void operator delete (void* mem)noexcept
    {
    	free(mem);
    }
    

    定位 new 表达式

    尽管 operator new 和 operator delete 函数一般用于 new 表达式,但是它们毕竟是标准库的两个普通函数,因此普通的代码也可以直接调用它们。

    使用定位 new 传递一个地址,此时定位 new 的形式如下所示:

    new (place_address) type
    new (place_address) type(initializers)
    new (place_address) type[size]
    new (place_address) type[size]{braced initializer list}
    

    place_address 必须是一个指针,同时在 initializers 中提供一个可能为空以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。

    当仅通过一个地址值调用时,定位 new 使用 operator new(size_t,void*) 分配它的内存。这是一个无法自定义的 operator new 版本。该函数不分配任何内存,它只是简单返回指针的实参,然后由 new 表达式负责在指定的地址初始化对象以完成整个工作。事实上,定位 new 允许在一个特定的、预先分配的内存地址上构造对象。

    尽管在很多时候使用定位 new 与 allocator 的 constructor 成员非常相似,但是他们之间有一个重要的区别:传给 constructor 的指针必须指向同一个 allocator 对象分配的空间,但是传给定位 new 的指针无须指向 operator new 分配的内存。实际上传给定位 new 表达式的指针甚至不需要指向动态内存。

    显示的析构函数调用

    就像定位 new 与使用 allocate 类似一样,对析构函数的显示调用也与使用 destroy 很类似。

    既可以通过对象调用析构函数,也可以通过对象的指针或者引用调用析构函数:

    string *sp = new  string("a value");
    sp->~string();	//@ 直接调用析构函数
    

    调用析构函数可以清楚给定对象但是不会释放对象所在的空间。

  • 相关阅读:
    iOS开发之Xcode8兼容适配iOS 10资料整理笔记
    C#流概述
    C#回调实现的一般过程
    ASP.Net MVC的学习
    RAID基本知识
    Infiniband基本知识
    [转]开源实时视频码流分析软件:VideoEye
    [转]高分一号的落后与特色
    [转]MVC,MVP 和 MVVM 的图示
    图文助你打开MS SQL Serever的ldf和mdf文件
  • 原文地址:https://www.cnblogs.com/xiaojianliu/p/12467073.html
Copyright © 2011-2022 走看看