安全放置新&显式析构函数调用

时间:2010-06-07 23:38:50

标签: c++ constructor destructor placement-new

这是我的代码示例:

template <typename T> struct MyStruct {
    T object;
}

template <typename T> class MyClass {
    MyStruct<T>* structPool;
    size_t structCount;

    MyClass(size_t count) {
        this->structCount = count;
        this->structPool  = new MyStruct<T>[count];
        for( size_t i=0 ; i<count ; i++ ) {
            //placement new to call constructor
            new (&this->structPool[i].object) T(); 
        }
    }

    ~MyClass() {
        for( size_t i=0 ; i<this->structCount ; i++ ) {
            //explicit destructor call
            this->structPool[i].object.~T(); 
        }
        delete[] this->structPool;
    }
}

我的问题是,这是一种安全的方法吗?在某种情况下我会犯一些隐藏的错误吗?它适用于每种类型的物体(POD和非POD)吗?

4 个答案:

答案 0 :(得分:7)

不,因为你的构造函数和析构函数都被调用了两次。因为你有这个:

template <typename T> struct MyStruct {
    T object;
}

构造MyStruct<T>时,编译将构造内部T,当您删除对象时,内部T将自动调用析构函数。

对于此示例,不需要放置新的或显式的析构函数调用。

如果您分配原始内存,Placement new将非常有用。例如,如果您将新内容更改为:

this->structPool  = new char[sizeof(T) * count];

然后你想要放置new和explict析构函数调用。

答案 1 :(得分:1)

不,这肯定不是远程安全的方法。对非POD new MyStruct<T>[count]执行T时,数组中的每个MyStruct<T>对象都已经默认构造,这意味着object成员的构造函数会被自动调用。然后,您尝试在其上执行就地构造(通过值初始化)。结果行为未定义。

删除存在同样的问题。

你想要实现的目标是什么?只需执行new MyStruct<T>[count]()(注意额外的空()),它就已经为数组的每个元素执行了值初始化(正是你之后“手动”执行的操作)。为什么你觉得你必须通过就地施工来做到这一点?

同样,当你做

delete[] this->structPool;

它会自动为数组中的每个MyStruct<T>::object成员调用析构函数。无需手动完成。

答案 2 :(得分:0)

  1. 记住new将始终调用构造函数,无论它是否是放置。
  2. - 所以你的代码使用了两次。这将调用构造函数两次。如果你想避免这种情况,请:

    将您的第一个新内容更改为malloc(或任何类型的分配)

    删除您的第二个展示位置

    1. 要删除数组中的对象,最好的方法是:调用每个对象的析构函数;释放记忆。
    2. - 所以你可以这样做:

      如果您使用新的[]

      ,请使用删除[]删除对象

      如果您使用malloc和placement new

      ,请调用每个析构函数并自由地执行C样式

答案 3 :(得分:0)

template <typename T> class MyClass {
    void* structPool;
    size_t structCount;

    MyClass(size_t count)
      : structPool(new char[sizeof(T)*count], structCount(count)
    {
        //placement new to call constructor
        for( size_t i=0 ; i<count ; i++ )
            new (structPool+i*sizeof(T)) T(); 
    }

    ~MyClass() {
        //explicit destructor call
        for( size_t i=0 ; i<structCount ; i++ )
            reinterpret_cast<T*>(structPool+i*sizeof(T))->~T(); 
        delete[] structPool;
    }
}

请注意,这不是异常安全的:如果其中一个构造函数导致异常,则它不会调用已构造的对象上的析构函数,并且会泄漏内存。当其中一个析构函数抛出时,这也会失败。

在您最喜欢的std lib实现中查看std::vector,以了解如何正确执行此操作。但是,这导致了一个问题: 为什么要首先执行此操作?
std::vector已经做到了这一切,做对了,你可以开箱即用,每个看你代码的人都会立即理解它:

template <typename T> class MyClass {
    std::vector<T> data_;
    MyClass(size_t count) : data() {data.resize(count);}
    //~MyClass() not needed
}