I have the following code that intends to create an array, but without default initialization of its objects. I would like to forward perfectly to placement new, which seems to happen, but I find that the objects' destructor is called inside the emplace function.
#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;
}
The result of running this program is as follows:
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
It shows that the objects are constructed once and destructed twice (which obviously is UB), once inside the emplace function, and then once at destruction of the NoInitArray.
The question is "Why is the destructor of my Int object called inside the emplace function"?
Compiler, latest Clang/LLVM on Windhoze.
EDIT1: I've added move and copy constructors to the Int struct, now the count matches, i.e. 2 constructions and 2 destructions.
EDIT2: Changing the Placement New line from new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
to new ( m_array + idx_ ) T ( value_ ... );
avoids the superfluous construction/destruction, without the need for a move constructor.
EDIT3: Just for future readers. As per above, the ~NoInitArray() leaks memory. Calling delete on m_array is bad news as well as this calls (in Clang/LLVM) the destructor of m_array [ 0 ] (but as far as I've understood now, that is in no way guaranteed, i.e. UB). std::malloc/std::free seems to be the way to go, but some say that if you do that all hell will break lose, and one may lose a leg.
"It shows that the objects are constructed once and destructed twice" is not true. The output X move constructed
should be included as one construction so the constructions are twice.
The line
new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
should be
new ( m_array + idx_ ) T ( std::forward<Args&&> ( value_ )... );
std::forward<T>(value_)
calls the constructor when T=Int
, and this temporary object is moved, so there is an extra move constructor call.
EDIT
In your edit 2 you replace the line without std::forward
anymore. In this case, OK, but the differences emerge when you call the emplace
like this
nia.emplace ( 0, Int(0) );
Without std::forward
, new T(value_...)
would call the copy constructor, while new T(std::forward<Args&&>(value_)...)
would call the move constructor.
EDIT-2
It should be new T(std::forward<Args>(value_)...)
. Thanks to @Constantin Baranov.
I think the constructor and destructor are called in the step: std::forward<T> ( value_ )
in new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... )
.
The std::forward<T>(value_)
will create a temp value T.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With