代码之家  ›  专栏  ›  技术社区  ›  uray

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

  •  1
  • uray  · 技术社区  · 16 年前

    这是我的代码示例:

    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;
        }
    }
    

    我的问题是,这样做安全吗?在某种情况下,我会犯一些隐藏的错误吗?它是否适用于所有类型的物体(吊舱和非吊舱)?

    4 回复  |  直到 14 年前
        1
  •  7
  •   R Samuel Klatchko    16 年前

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

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

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

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

    如果您分配了原始内存,那么新位置将非常有用。例如,如果您将新的更改为:

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

    然后您将要放置新的和明确的析构函数调用。

        2
  •  1
  •   AnT stands with Russia    16 年前

    不,这绝对不是一种远程安全的方法。当你这样做的时候 new MyStruct<T>[count] 对于非豆荚 T ,每一个 MyStruct<T> 数组中的对象已得到默认构造,这意味着 object 自动调用成员。然后尝试在其上执行就地构造(通过值初始化)。结果行为未定义。

    删除操作也存在同样的问题。

    你想达到什么目的?只要做 new MyStruct<T>[count]() (注意多余的空 () )它已经为数组的每个元素执行了值初始化(这正是您之后要“手动”执行的操作)。为什么你觉得你必须在现场施工?

    同样,当你这样做的时候

    delete[] this->structPool;
    

    它会自动调用每个函数的析构函数 MyStruct<T>::object 数组中的成员。不需要手动操作。

        3
  •  0
  •   xis    16 年前
    1. 记住new将始终调用构造函数,不管它是否是placement。

    --所以您的代码使用了两次new。这将调用构造函数两次。如果要避免这种情况,请执行以下操作之一:

    把你的第一个新的变成malloc(或任何一种alloc)

    删除第二个新位置

    1. 要删除数组中的对象,最好的方法是:调用每个对象的析构函数;释放内存。

    --所以你可以这样做:

    如果使用新的[],请使用delete[]删除对象。

    如果使用malloc和placement new,则调用每个析构函数并执行C样式的free

        4
  •  0
  •   Community Mohan Dere    14 年前
    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::vector 在您最喜欢的std lib实现中,以便了解如何正确地执行此操作。然而,这就引出了一个问题: 你为什么一开始就想这么做?
    STD::载体 已经做到了所有这些,正确了吗,您可以开箱即用,并且每个查看您的代码的人都会立即理解它:

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