安置新的和完美的转发

时间:2016-06-12 05:31:16

标签: c++ c++11

我有以下代码打算创建一个数组,但没有默认的对象初始化。我想完美地转发到放置new,这似乎发生了,但我发现对象的析构函数在emplace函数内部被调用。

#include <iostream>
#include <memory> // std::uninitialized_copy, std::allocator...
#include <utility> // std::move...
#include <bitset>


struct Int {

    int i;

    Int ( ) : i ( -1 ) { std::cout << "default constructed\n"; }
    Int ( const int i_ ) : i ( i_ ) { std::cout << i << " constructed\n"; }
    Int ( Int && int_ ) : i ( std::move ( int_.i ) ) { std::cout << i << " move constructed\n"; }
    Int ( const Int & int_ ) : i ( int_.i ) { std::cout << i << " copy constructed\n"; }
    ~Int ( ) { std::cout << i << " destructed\n"; i = -1; }
};


template <typename T, size_t S = 64>
class NoInitArray {

    std::bitset<S> m_used;

    T *m_array = reinterpret_cast < T* > ( ::operator new ( sizeof ( T ) * S ) );

public:

    T const &operator [ ] ( const size_t idx_ ) const {

        return m_array [ idx_ ];
    }

    NoInitArray ( ) { }

    ~NoInitArray ( ) {

        for ( size_t idx = 0; idx < S; ++idx ) {

            if ( m_used [ idx ] ) {

                reinterpret_cast< const T* > ( m_array + idx )->~T ( );
            }
        }
    }

    template<typename ...Args>
    void emplace ( const size_t idx_, Args &&... value_ ) {

        std::cout << "start emplace\n";

        m_used [ idx_ ] = 1;

        new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );

        std::cout << "end emplace\n";
    }
};


int main ( ) {

    NoInitArray<Int> nia;

    nia.emplace ( 0, 0 );
    nia.emplace ( 1, 1 );

    std::cout << nia [ 1 ].i << std::endl;

    nia.emplace ( 2, 2 );

    return 0;
}

运行该程序的结果如下:

start emplace
0 constructed
0 move constructed
0 destructed
end emplace
start emplace
1 constructed
1 move constructed
1 destructed
end emplace
1
start emplace
2 constructed
2 move constructed
2 destructed
end emplace
0 destructed
1 destructed
2 destructed

它表明对象构造一次并被破坏两次(显然是UB),一旦进入emplace函数,然后一次破坏NoInitArray。

问题是“为什么我的Int对象的析构函数在emplace函数内部被调用”?

编译器,最新的Windhoze Clang / LLVM。

EDIT1:我已经将移动和复制构造函数添加到Int结构中,现在计数匹配,即2个构造和2个析构。

EDIT2:更改展示位置从new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );new ( m_array + idx_ ) T ( value_ ... );的新行避免了多余的构造/破坏,无需移动构造函数。

EDIT3:仅供未来的读者使用。如上所述,~NoInitArray()泄漏内存。在m_array上调用delete是坏消息,并且这个调用(在Clang / LLVM中)是m_array [0]的析构函数(但据我现在所理解,这绝不是保证,即UB)。 std :: malloc / std :: free似乎是要走的路,但有些人说,如果你这样做,那么一切都会失败,而且可能会失去一条腿。

2 个答案:

答案 0 :(得分:4)

“它显示对象构造一次并且被破坏两次”不是真的。输出X move constructed应作为一个结构包含在内,因此构造是两次。

该行

new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );

应该是

new ( m_array + idx_ ) T ( std::forward<Args&&> ( value_ )... );

std::forward<T>(value_)T=Int时调用构造函数,并移动此临时对象,因此有一个额外的移动构造函数调用。

修改

在您的编辑2中,您不再使用std::forward替换该行。在这种情况下,好的,但是当您像这样调用emplace

时会出现差异
    nia.emplace ( 0, Int(0) );

如果没有std::forwardnew T(value_...)会调用复制构造函数,而new T(std::forward<Args&&>(value_)...)会调用移动构造函数。

EDIT-2

应该是new T(std::forward<Args>(value_)...)。感谢@Constantin Baranov。

答案 1 :(得分:4)

我认为构造函数和析构函数是在std::forward<T> ( value_ )中的new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... )步骤中调用的。

std::forward<T>(value_)将创建一个临时值T.