More Effective C++ 条款8 了解各种不同意义的new和delete

1. new operator:new操作符,用于动态分配内存并进行初始化;

    placement new(定位new):new operator的另外一种用法 ,在已分配的内存上构造对象; 

    operator new:标准库的函数,只分配内存不进行初始化(或者传递一个可用的内存地址),可以自己进行重载,也可以主动调用。

    注意:new operator是操作符,placement new是这个操作符的一种用法,而operator new是标准库中的函数,new operator调用了 operator new。 

2. new operator 实际进行两个动作:1)调用operator new 获得一块可用的内存地址   2) 初始化对象

    new operator是不能被重载的,无论怎样,它总进行这两个步骤,可以被重载的是operator new,即用来获取可用内存的函数,通过重载operator new可以简介优化new operator。

3. 对于operator new,其用法根据new operator的用法而有所不同,如果是形如”A *p=new A“的普通的new用法,那么内存实际上由operator new进行分配并传回一个地址,如果是形如”A *p=reinterpret_cast<A*>(new(ptr) A())"的定位new的用法,那么operator new基本上不做任何事情,只是将传入的ptr(指向一块可用内存的指针)参数返回而已,供接下来的构造函数使用。

    也就是说普通new表达式自己分配内存并构造对象,定位new表达式在已分配好的内存上构造对象(这也就是为什么2说new operator"获得"可用的内存地址而不是“分配内存",因为在定位new的使用下,operator是不分配内存的),但无论怎样,都要进行2所说的两个动作。

    要注意的是默认的(即全局作用域中的)的operator 一共有两个重载版本,对应于普通new表达式和定位new的两个用法,如果要对operator new进行重载,那么由于作用域覆盖原则,重载的operator new会覆盖全局作用域中的两个operator new,所以重载的时候务必要重载两个(如何重载见4),否则要不默认的new表达式无法使用要不定位new无法使用。当然也可以在new操作符前加上::表示使用的是全局版本。

4. 重载的operator new必须具有void* 返回类型而且第一个参数必须为size_t类型(就算是int类型都不行),此外根据是定位new还是普通new,operator new决定是否增加指针参数(只要满足返回类型为void*而且第一个参数类型为size_t,其他参数随便加,只不过普通new表达式只调用只含有一个size_t参数的operator new版本,而定位new表达式只调用含有一个size_t类型和指针参数类型的operator new版本,自己重载的其他版本可以按需要添加参数)

    对于定位new中的operator new,第一个size_t类型的参数其实是没有用的,它的主要功能是传递一个可用内存的地址。

5. 此外还有operator delete,operator new[]以及operator delete delete[],它们的作用与operator new稍有不同

    operator delete: 用于delete操作符(delete操作符实际上也进行两个步骤,析构被delete的对象,调用operator delete释放内存)由于delete操作符的用法只有一种,因而operator delete的作用也就只有一种:释放内存

        重载的operator delete必须具有返回类型void,它可以定义为接受单个void*类型形参,也可以定义为接受void* 和size_t类型两个形参,如果提供了size_t类型的形参,就有编译器用第一个形参所指对象的大小自动初始化size_t形参(但是第一个形参不是void*型么?void* 型是不具备所指对象大小的信息的,可能是编译器又在背后做了其他事情吧),对于编译器自动初始化size_t形参的做法,这种类似的做法已经出现过许多次了,比如定位new中的operator的第一个size_t形参,以及后自增操作符大的哑元参数等(编译器确实背着我们干了许多事)

    operator new[]:operator new 的数组版本,与new操作符创建数组的用法对应,operator new[]与operator new有一定区别,“重载的operator new[]必须具有返回类型void*,并且接受的第一个形参类型为size_t,用表示存储特定类型给定数目元素的的数组的字节数值自动初始化操作符的size_t形参”,注意,operator new[]返回的地址之前还有4个字节用来存储元素数目(编译器背着我们做的,就算自己重载了也一样),代码示例如下(参考来源:http://bbs.chinaunix.net/thread-1853264-1-1.html,感谢liwangli1983的指引):

#include<iostream>
using std::cout;
using std::endl;
class X
{
        public:
                ~X() { cout << "del" << endl; }
        private:
                int a;
                int b;
};

void *operator new[](size_t size)
{
        void *p = malloc(size);
        cout << "size_t: " << size << endl;
        cout << "address: "<< p << endl;
        return p;
}

int main(void)
{
        X *q = new X[5];
        cout << "address\': "<< q << endl;
        cout << *(reinterpret_cast<int *>(q) - 1) << endl;
        delete [] q;
        system("pause");
        return 0;
}
View Code
结果如下:
#include<iostream>
using std::cout;
using std::endl;
class X
{
        public:
                ~X() { cout << "del" << endl; }
        private:
                int a;
                int b;
};

void *operator new[](size_t size)
{
        void *p = malloc(size);
        cout << "size_t: " << size << endl;
        cout << "address: "<< p << endl;
        return p;
}

int main(void)
{
        void *q =operator new[](5*sizeof(X)); //注意:这里是直接调用operator new[] !
        cout << "address\': "<< q << endl;
        operator delete[](q);
        system("pause");
        return 0;
}
View Code
结果如下:

注意,直接调用operator new[]不会有上述行为,这也就是为什么使用delete[]只能delete掉一个经由new操作符创建的数组,因为new操作符的使用告诉了编译器动态分配的内存上的对象类型,同时也暗示了如何构造以及析构,而operator new以及operator new[]只提供了内存的小的信息而不关心在这块内存上到底构造了什么。(注:个人认为除了利用new操作符创建对象数组时调用的是operator new[]外,operator new[]和operater new没有差别,如有错误欢迎批评指教!)

6 .尽管operator new[]既可以直接调用也可以经由new操作符调用,但是具体操作上有细微差别,由new操作符调用实际上传递了更多信息,包括在其上构建的对象的类型和数目,因而可以直接使用delete[]来析构对象并释放内存,但是通过调用operator new[]来分配内存却并不包含这些信息,因而不能使用delete[]来析构对象并释放经由operator new[]得到的内存,如果这么做会发生未定义行为。值得强调的是,不能使用delete[]释放经由operator new[]得到的内存但却可以使用delete操作符释放经由operator new操作符得到的内存(原因在于delete[]需要更多信息,包括对象的数目,而这些信息只有在调用new操作符创建数组时才具备,而delete由于释放的是单个对象,因而通过要delete的指针的类型就可以得知所需信息!),代码示例如下:

//错误代码,operator new[]或operator new得到的内存不可以经由delete[]释放
#include<iostream>
using std::cout;
using std::endl;
class X{
    public:
        ~X() { cout << "del" << endl; }
        int& operator=(int x){ a = x; }
    private:
        int a;
        int b;
};
int main(void)
{
    void *ptr = operator new[](10*sizeof(X));
    X* ptr_x = reinterpret_cast<X*>(ptr);
    for (int cou = 0; cou < 10; ++cou){
        new(ptr_x)X();
        ptr_x += 1;
    }
    delete[] reinterpret_cast<X*>(ptr);//会在此处发生错误!
    system("pause");
    return 0;
}
View Code
//operator new得到的单块内存可以经由delete释放
#include<iostream>
using std::cout;
using std::endl;
class X{
    public:
        ~X() { cout << "del" << endl; }
        int& operator=(int x){ a = x; }
    private:
        int a;
        int b;
};
int main(void)
{
    void *ptr = operator new(sizeof(X));
    X* ptr_x = reinterpret_cast<X*>(ptr);
    delete ptr_x;   //运行正常
    system("pause");
    return 0;
}
View Code

 7.总结:

    1) placement new(定位new)是new operator的另一种使用方法,用于在已分配好的内存上构造对象,它与普通的new表达式调用的是不同的operator new。

    2) operator new 只分配内存而不构造对象,对象的构造,析构以及内存的释放由自己负责,提升了效率也加重了负担。

    3) operator new 的使用可以与operator delete的使用相配合,new operator的使用与delete operator的使用相配合,但使用operator new分配的单个的内存也可以与delete operator配合使用;

        operator new[]的使用绝对不可以与delete []相配合,也就是说delete []只能释放经由new operator得到的连续内存,否则会发生未定义行为!

    4) operator new[]直接调用与经由new operator调用效果是不同的,直接调用operator new[]相当于调用operator new,而调用new operator构造数组编译器还做了其他事情以便于与delete []的使用相配合。

转载于:https://www.cnblogs.com/reasno/p/4601843.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值