Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intel 2015 compiler bug, RAII destruction not correct, is this a bug or am I doing something wrong?

Tags:

c++

c++11

icc

I've got a test case where I have a class with 3 subobjects (A, B and C), and the 2nd subobject B throws an exception during construction. As I understand C++, the compiler should rewind the construction of the big class and destroy the 1st object A, but not the 2nd (B) or 3rd (C) objects.

What I see is that if I use "In-class initialization" of the first object A, then instead of the first object A getting destroyed, the 3rd object C gets destroyed. Of course it is VERY BAD to destroy an object that has not been constructed! If, for example, C was a std:unique_ptr<T>, it will probably signal a segmentation violation when it tries to free a garbage pointer.

If I use old school "member initialization", then this problem doesn't happen.

I don't see this with gcc 4.8

Here's the code. The class D exposes the bug. The class E should have identical function, but it does not expose the bug.

#include <iostream>

using namespace std;


struct A {
    A(const string& x) : x_(x) { cout << "A::A()" << (void*)this <<endl; }
    ~A() { cout << "A::~A() " << (void*)this<< endl;}
    string x_;
};

struct B {
    B(const A& a)  { cout << "B::B()" << endl; throw "dead"; }
    ~B() { cout << "B::~B()" << endl;}
};

struct C {
    C()  { cout << "C::C()" << endl; }
    ~C() { cout << "C::~C()" << endl;}
};


struct D  {
    A a{"foo"}; // "new school In-class initialization"
    B b{a};
    C c;
    D() { cout <<"D::D()" << endl; }
    ~D() { cout <<"D::~D()" << endl; }
};

struct E {
    A a;
    B b;
    C c;
    E()
        :a{"foo"}  // "old school member initialization"
        ,b(a)
        { cout <<"E::E()" << endl; }
    ~E() { cout <<"E::~E()" << endl; }
};

int main()
{
   try {
       D d;
   }
   catch(...)
   {
       cout << "got exception" << endl;
   }

   try {
       E e;
   }
   catch(...)
   {
       cout << "got exception" << endl;
   }

   return 0;
}

Here is the output. I expect to see A constructed, B partially constructed then throws, then A destroyed, but that is not what I see for the D case.

$ icpc -std=c++11 test.cpp
$ ./a.out
A::A()0x7fffe0a5ee90
B::B()
C::~C()
got exception

A::A()0x7fffe0a5eea0
B::B()
A::~A() 0x7fffe0a5eea0
got exception

-- update --

The section of the standard that describes what should happen is 15.2.3

For an object of class type of any storage duration whose initialization or destruction is terminated by an exception, the destructor is invoked for each of the object’s fully constructed subobjects, that is, for each subobject for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.

like image 305
Mark Lakata Avatar asked Jun 05 '15 19:06

Mark Lakata


2 Answers

This is definitely a compiler bug, and you've answered your own question with the correct reference from the standard: [except.ctor]/3, with added emphasis:

For an object of class type of any storage duration whose initialization or destruction is terminated by an exception, the destructor is invoked for each of the object’s fully constructed subobjects, that is, for each subobject for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.

Where:

The principal constructor is the first constructor invoked in the construction of an object (that is, not a target constructor for that object’s construction).

C has not been fully constructed - its principal constructor has not even been called yet - so it's destructor should not be called yet.

like image 91
Barry Avatar answered Sep 22 '22 09:09

Barry


Intel has confirmed this is an issue.

The compiler I used was

icpc (ICC) 15.0.2 20150121

You can follow the Intel forum for updates on when it is resolved.

https://software.intel.com/en-us/comment/1827356

like image 45
Mark Lakata Avatar answered Sep 24 '22 09:09

Mark Lakata