【问题标题】:Safe placement new & explicit destructor call安全放置新的和显式的析构函数调用
【发布时间】:2011-02-28 22:49:29
【问题描述】:

这是我的代码示例:

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

我的问题是,这是一种安全的方法吗?我是否在某些情况下犯了一些隐藏的错误?它适用于所有类型的对象(PO​​D 和非 POD)吗?

【问题讨论】:

  • 请注意,您的代码不是异常安全的,如果其中一个构造函数失败,则会泄漏对象和内存。

标签: c++ constructor destructor placement-new


【解决方案1】:

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

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

当您构造MyStruct&lt;T&gt; 时,编译将构造内部T,而当您删除对象时,内部T 将自动调用析构函数。

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

如果您分配原始内存,新位置将很有用。例如,如果您将新的更改为:

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

那么你会想要放置新的和显式的析构函数调用。

【讨论】:

  • 如果this-&gt;structPool = new MyStruct&lt;T&gt;[count];this-&gt;structPool = (MyStruct&lt;T&gt;*)malloc(sizeof(MyStruct&lt;T&gt;)*count); 替换会怎么样。在这种情况下,我们需要放置新的显式析构函数?
  • 是的,你可以,但你会为 MyStruct 本身调用构造函数/析构函数,而不是为 MyString.object。
  • @uray - 是的,如果您使用 malloc 获取原始内存,则需要放置新的/显式析构函数。也就是说,您似乎正在寻找使用放置新/显式析构函数的理由,而不是真正需要它。
  • @Samuel:哈哈,不,我不是在找理由,我只是为了确保我的理解是正确的。
  • @uray - 明白了。我的建议是摆脱MyStruct&lt;&gt;,因为这只会混淆问题。
【解决方案2】:

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

删除也存在同样的问题。

你想要达到什么目的?只需执行new MyStruct&lt;T&gt;[count]()(注意额外的空()),它就会为数组的每个元素执行值初始化(这正是您之后尝试“手动”执行的操作)。为什么你觉得必须就地施工?

同样,当你这样做时

delete[] this->structPool;

它会自动为数组中的每个MyStruct&lt;T&gt;::object 成员调用析构函数。无需手动操作。

【讨论】:

  • 如果T的构造函数需要构造参数,TT()T(arg,..)构造函数,但我需要在构造过程中提供参数怎么办?我还能做this-&gt;structPool = new MyStruct&lt;T&gt;[count]; 的建筑风格吗?
  • 额外的空()是强制性的吗?如果我不使用它会发生什么,它会对每个元素进行值初始化吗?
  • @uray:如果T没有默认构造函数,MyStruct的定义将无法编译。如果T 有默认构造函数,但你想使用其他构造函数,那么你会遇到上述双重构造(和双重破坏)的问题。
  • @uray:额外的空() 不是强制性的。它来自你的代码。在我的回答中,它就在那里,因为 you 在你的placement-new 调用中使用了空的(),即你使用了new (&amp;...) T()。空的() 表示值初始化。再次,我提到它只是因为你自己使用它。如果您在没有空的() 的情况下进行了新的展示位置,即只有new (&amp;...) T,那么等效的数组变体也不会有空的()。所以,这对你来说真的是一个问题,这取决于你想在这里使用什么样的初始化。
  • @andrey:我知道它需要默认构造函数并且它有,但是假设我只需要使用参数调用“构造函数”来正确初始化 T 的值,通过放置 new 进行第二次构造是不只是调用构造函数或者它会做更多吗?因为我的理解是placement new 不分配任何内存空间所以我假设我可以做placement new 只是为了调用构造函数。
【解决方案3】:
  1. 记住 new 将始终调用构造函数,无论是否放置。

-- 所以你的代码使用了两次 new。这将调用构造函数两次。如果你想避免这种情况,要么:

将您的第一个 new 更改为 malloc(或任何类型的 alloc)

删除您的第二个展示位置新

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

-- 所以你可以这样做:

如果您使用的是 new[],请使用 delete[] 删除对象

如果您使用 malloc 和放置 new,则调用每个析构函数并免费执行 C 样式

【讨论】:

    【解决方案4】:
    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
    }
    

    【讨论】:

      猜你喜欢
      • 2016-06-12
      • 1970-01-01
      • 2021-01-28
      • 1970-01-01
      • 2014-04-15
      • 2018-01-17
      • 1970-01-01
      相关资源
      最近更新 更多