Double Delete in Initializer_List VS 2013

Double delete in initializer_list vs 2013

The initializer_list behavior is buggy. In its destructor it calls a vector delete (a delete[]) of the entire range and then deletes the first entry in the array again.
This behavior is not part of the initializer_list class and looks like a compiler bug. initializer_list doesn't have a destructor and doesn't allocate the array used for the list. It just looks like a wrapper for a C array.

As for using the extra copy you see, it's caused by the vector resizing from during its initialization.
Here's your flow:

Init 00B7FAE8       // construct "foo"
Init 00B7FBE8 // construct "bar"
Copy 00E108A0 // copy "foo" to vector (capacity=1)
Copy 00E10AE8 (?????) // copy the above object to the resized vector (capacity = 2)
Deleting b 00E108A0 // delete the smaller vector buffer
Copy 00E10BE8 // copy "bar" from initialization_list to vector

Deleting b 00B7FBE8 // delete initialization_list in reverse order. this is "bar"
Deleting b 00B7FAE8 // last to delete. this is "foo"

Deleting b 00B7FAE8 (bug)

// later C::bs is destroyed

What you can see here is the initializing a vector via push_back is quite slow due to copying. This would be happen even if you've used the more elegant way:

C(initializer_list<B> bb) : bs(bb) {}

A faster (no extra copies) method is:

C(initializer_list<B> bb) {
bs.reserve(bb.size());
bs.insert(bs.end(), bb.begin(), bb.end());
}

Why first element is destroyed?

What happens is that a bug in VS2013 causes a double delete on the first item of the initializer_list.
Here's the flow:

  1. initializer_list is constructed.
  2. target vector is reserved a size of 1 and first item is copied (via copy constructor).
  3. vector grows slowly to initializer_list size.
  4. initializer_list is destroyed via a vector-destructor (i.e. delete[]). Last element is destroyed first.
  5. First element is destroyed again via scalar-destructor (i.e. delete).

I've seen this on another post and verified the behavior using a debugger.
See here

For VS2013, initializer_list is good for basic types only.

Crash in VS2013 SP5 when inserting initializer_list into vectorstring

You would be surprised if you try inserting 3 elements like x.insert(x.end(), {"e","f", "g"});. No crash, right!

This is a bug in MSVC 2013 and seems to be fixed in MSVC 2015. Here is an explanation to shed some light to where the problem occurs. If you try only declaring std::string a{ "a", "b" }; you will see that it compiles fine but it causes a crash. If you add one more element, then it won't compile at all. So, what happens with the insert is that it calls the string constructor instead of calling vector constructor.

VC++ vector as default parameter

VC2013 internal bug. It's not entirely up to date with new C++ constructs.

The problem surfaces in the debug delete code, in particular code that's checking against double deletes and deletion of non-heap-allocated objects. Since the relevant objects are std::vector and std::string, there are roughly four options:

  1. Bug in std::string implementation
  2. Bug in std::vector implementation
  3. Bug in dbgdel.cpp code which checks delete in debug mode
  4. Compiler bug in default argument code

Considering the two test cases, the first three are rather unlikely.

Edit2: From the duplicate: "The initializer_list behavior is buggy. In its destructor it calls a vector delete (a delete[]) of the entire range and then deletes the first entry in the array again)." initializer_list is part of the implementation, so this is indeed an internal error in VS2013.

std::unique_ptr deleted function, initializer_list - driven allocation

According to Paragraph 8.5.1/2 of the C++11 Standard:

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer listare taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. [...]

For each element, then, copy-initialization involves the creation of a temporary of the destination type, which is then use to copy-construct the element of the array.

However, your class contains a member whose type is an instance of unique_ptr, which is non-copyable. That makes your class non-copyable as well.

Moreover, although unique_ptr is moveable, your class is not, because the implicit generation of a move constructor by the compiler is suppressed by the presence of an explicitly defined destructor. If that were not the case (i.e., if you explicitly defined a move constructor for your class), copy-initialization would work (see 8.5/15).

Try changing the definition of WFactory as follows to see that:

class WFactory
{
public:
WFactory(const int i) : _w(new W1()) {}
WFactory(const char* s) : _w(new W2()) {}
WFactory(WFactory&& f) : _w(std::move(f._w)) {}
~WFactory() { _w.reset(nullptr); }
private:
std::unique_ptr<Widget> _w;
};

int main()
{
std::unique_ptr<Widget> a(new W1());
WFactory wf[] { 4, "msg" }; // OK
}

Abstract class as std::initializer_list object

Somewhat hackish approach using a initializer_list<base *>:

template<class... Ts>
void foo_everything(Ts&&... args){
std::initializer_list<base *> list = {&args...};
for(auto i : list) i->foo();
}

Then remove the braces from the call:

foo_everything(derived(1), derived(2), derived(3));

If you don't really need to convert to base * and perform a virtual call, and just want to call foo() on each object passed in, then we can use the usual pack-expansion-inside-an-initializer-list trick:

template<class... Ts>
void foo_everything(Ts&&... args){
using expander = int[];
(void) expander { 0, ((void) std::forward<Ts>(args).foo(), 0)...};
}


Related Topics



Leave a reply



Submit