美文网首页
理解C++ placement语法

理解C++ placement语法

作者: 一个淡定的程序员 | 来源:发表于2014-04-13 14:55 被阅读0次

    最近小组读书活动让我对 placement new 和 placement delete 有了更加深入的理解.

    关于new表达式

    C++ 提供了new关键字和delete关键字, 分别用于申请和释放内存空间, 其中new表达式的语法如下:

    new new-type-id ( optional-initializer-expression-list )
    

    书上说, new表达式做两件事情

    • 在堆(heap)空间上申请一块空间, 大小等于sizeof(new-type-id)
    • 在申请的空间上构建对象, 即调用对象的构造函数

    另外, 我了解到, 如果用户希望在自定义内存空间上构造对象, 可以调用另一个new表达式, 语法如下:

    new ( expression-list ) new-type-id ( optional-initializer-expression-list )
    

    具体使用起来, 就像这样:

    void *buffer = malloc(sizeof(ClassA));
    ClassA *ptr = new(buffer)ClassA();
    

    第二种new表达式就像第一种的特例, 只需要完成步骤2, 而把步骤1留给用户自理.

    关于delete表达式##

    C++ 提供的delete表达式的语法:

    delete type_pointer;
    

    书上说, delete表示完成两件事情:

    • 调用对象的析构函数,
    • 释放对象所在的内存空间, 返回给系统

    特别注意的是, 让type_pointer等于NULL, delete表达式也能正确功能, 但是对同一个指针, 重复调用delete将带来未定义错误

    标准 operator new 和 operator delete##

    C++ 标准的 new expression 内部调用的是标准的operator new(), 它的定义如下:

    void * operator new (std::size_t) throw(std::bad_alloc);
    

    operator new()是一个操作符或者函数, 它完成标准的allocation, 即内存分配.

    类似的, C++标准的delete expression 内部调用的是标准的operator delete(), 它的定义如下:

    void operator delete (void *) throw();
    

    operator delete()也是一个操作符或者函数, 它完成标准的deallocation, 即内存释放

    placement new 和 placement delete##

    C++标准的 new 表达式能完成大部分的需求, 但不是全部, 比如: 如何在已有的内存空间上创建对象, 标准 new 表达式做不了, C++也不支持直接在raw内存上调用对象的构造函数, 于是placement new就产生了, 取名placement, 也说明了它的历史来源, 即在原地构建一个新对象.

    当然原地创建对象只是一部分, placement new有更广大的外延, 而且placement new expression和placement operator new(), 通常都被笼统的成为placement new, 混淆了概念.

    什么是placement operator new(), 它首先是一个函数, 而且是标准operator new()的一个重载函数. wiki中这么定义它:

    The "placement" versions of the new and delete operators and functions are known as placement new and placement delete.

    什么是placement new expression, 它属于C++语法, 类似于标准的new expression, 不同的是,内部调用的是相应的placement operator new(), wiki上这么定义它:

    Any new expression that uses the placement syntax is a placement new expression

    就像标准 new expression 调用的是标准 operator new()一样, placement new expression 调用的是placement版本的operator new(), 看起来很简单直白, 混在一起似乎也问题不大, 但是看看placement delete, 它就表示 placement operator delete() 函数, 因为根本不存在placement delete expression, 当我们调用

    delete pObject;
    

    我们调用的是标准的operator delete(). 为什么没有placement delete expression, C++的缔造者给出的解释是:

    The reason that there is no built-in "placement delete" to match placement new is that there is no general way of assuring that it would be used correctly.

    既然没有placement delete expression, 那为啥还需要placement operator delete(), 一个重要的原因是, C++需要placement operator delete()和placement operator new()成双成对. 假设一种情况:

    当你调用placement new expression构建对象, 结果在构造函数中抛出异常, 这个时候怎么办, C++ 只能调用相应的placement operator delete(), 释放由placement operator new()获取的内存资源, 否则就会有内存泄露. 通过下面的例子可以感受一下:

    #include <cstdlib>
    #include <iostream>
    
    struct A {} ;
    struct E {} ;
    
    class T {
    public:
        T() { throw E() ; }
    } ;
    
    void * operator new ( std::size_t, const A & )
        {std::cout << "Placement new called." << std::endl;}
    void operator delete ( void *, const A & )
        {std::cout << "Placement delete called." << std::endl;}
    
    int main ()
    {
        A a ;
        try {
            T * p = new (a) T ;
        } catch (E exp) {std::cout << "Exception caught." << std::endl;}
        return 0 ;
    }
    

    placement operator new() 和 placement operator delete()

    上文只是界定了expression和operator的区别, 那什么样的函数才叫placement operator new()和placement operator delete(), 他们和标准operator new()以及operator delete()的区别就添加了自定义参数. 但是必须遵守以下规则.

    对于placement operator new(), 它的第一个函数参数必须是std::size_t, 表示申请的内存的大小; 对于placement operator delete(), 它的第一个函数参数必须是void *, 表示要释放的对象指针. 比如:

    void * operator new (std::size_t) throw(std::bad_alloc);    // 标准版本
    void * operator new (std::size_t, const std::nothrow_t &) throw(); // placement 版本
    void operator delete (void *) throw(); // 标准版本
    void operator delete (void *, const std::nothrow_t &) throw(); // placement 版本
    

    请注意nothrow版本的new, 也是C++标准, 它就是通过placement new和placement delete实现的.相应的nothrow 版本的new expression是这样:

    T *t = new(std::nothrow) T;
    

    在用户自定义空间上构建对象, 是placement new的本意, 它也被做到C++标准中, 作为default placement:

    void * operator new (std::size_t, void * p) throw() { return p ; }
    void operator delete (void *, void *) throw() { }
    

    相应的 placement new expression 使用起来就是这样:

    void *buffer = malloc(sizeof(ClassA));
    ClassA *ptr = new(buffer)ClassA();
    

    mempool用到的placement new

    实际应用中, 内存池用到最多, 值得一讲, 假设一个内存池的定义如下:

    class Arena {
    public:
        void* allocate(size_t);
        void deallocate(void*);
        // ...
    };
    

    通过定义placement new和placement delete作用于Arena

    void* operator new(size_t sz, Arena& a)
    {
        return a.allocate(sz);
    }
    void operator delete(void *ptr, Arena& a)
    {
        return a.deallocate(ptr);
    }
    

    创建对象:

    Arena a();
    X *p = new(a)X;
    

    现在的问题是怎么delete, 因为我们不能调用

    delete p; 
    

    这样只能启用的标准的operator delete(), 而不是针对Arena的placement版本, 为此, 我们只能这么做:

    p->~X();
    operator delete(p, a);
    

    或者把两个操作封装成一个template:

        template<class T> void destroy(T* p, Arena& a)
        {
            if (p) {
                 p->~T();       // explicit destructor call
                 a.deallocate(p);
            }
        }
    

    然后这么调用

    destroy(p,a);
    

    参考

    相关文章

      网友评论

          本文标题:理解C++ placement语法

          本文链接:https://www.haomeiwen.com/subject/vjeltttx.html