Dynamic memory allocation and deleted definitions

Hi all,

clang version 3.4 (194264)
Target: i386-pc-linux-gnu
Thread model: posix

gcc version 4.8.1

This is with respect to the following test code:

struct A
{
~A() = delete;
};

int main()
{
A* ap = new A[5];
}

While g++ compiles it without any errors , clang throws an error:

test.cpp:9:11: error: attempt to use a deleted function
A* ap = new A[5];
^
test.cpp:3:3: note: function has been explicitly marked deleted here
~A() = delete;
^
1 error generated.

My question is:

How does the destructor come into picture when we are not explicitly deleting the memory allocated?

The standard says: A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

But I suppose the destructor never gets called in this case. So ideally this test case should compile without any errors. Please correct me if my understanding is incorrect.

Interestingly the error is not thrown when I tweak the test case to allocate a single instance instead of an array. Something like this:

struct A
{
~A() = delete;
};

int main()
{
A* ap = new A;
}

This piece of code compiles fine with both g++ and clang++ as expected.

Is this a potential bug in clang or am I missing something obvious??

Thanks,
Rahul

I'd have to check wording to see the finer points, but certainly the issue
you are hitting is that, if A's ctor throws on one of the constructed
objects in the array, the dtor for the previous array elements needs to be
run.

I'm not sure if the language requires that this check only occur for
non-nothrow ctors or not.

gcc only errors when there’s a non-trivial default constructor. - ½

But why will the destructor get called if the array is not explicitly deleted?

Consider the following piece of code without the deleted definition.

#include<stdio.h>

struct A
{
~A() {
printf (“Inside destructor\n”);
}
};

int main()
{
A* ap = new A[5];
delete []ap;
}

On executing this “Inside destructor” gets printed 5 times.

But if we remove the delete statement, nothing gets printed i.e the destructor is never run.

I did not get what you meant by saying “if A’s ctor throws on one of the constructed objects in the array, the dtor for the previous array elements needs to be run”?

Thanks,
Rahul

Are you familiar with C++ exceptions?

Try something like this:

int i = 0;

struct A {
  A() { if (++i == 2) { throw 0; } }
  ~A() { printf("Inside destructor\n"); }
};

int main() {
  A *ap = new A[2];
}

Even without the delete statement, you should see "Inside destructor" print
once.

As I said, it's possible that if the constructor is 'nothrow' then Clang is
meant to not need the dtor here, I haven't checked the exact wording of the
standard.

Because if you new[] an array of objects, and the constructor of one of the objects throws an exception, then the already constructed objects will get destructed (destroyed?).

Example:
#include <iostream>

int i = 0;
struct A
{
     A() { id = i++; if(id == 5) throw std::exception(); }
     ~A() { std::cout << "Destroying " << id << "\n"; }

     int id;
};

int main()
{
     try
     {
     A *a = new A[10];
     }
     catch(...) {}
     return 0;
}

  - ½

Thanks David, Halfdan for your valuable inputs.
Just need some more clarification so that I get the complete thing.

Yes I understood that when we new[] an array of objects and the constructor of one of the objects throws, the already constructed objects should be destructed as the complete array construction could not get through successfully.

But assume if the constructor is not throwing, and all the objects of the array get constructed completely, than where does the need of the destructor call arise from?

Is it the possibility of a runtime throw which prevents the compiler from compiling a deleted destructor definition in case of an array allocation?

Also what exactly is the difference in this context when we define a default constructor in our class vs a synthesized default constructor in our class?

g++ seems to accept the synthesized version whereas errors when one defines a default constructor(as Halfsan mentioned above), whereas clang rejects both the versions. Who is at fault here?

Thanks,
Rahul

But assume if the constructor is not throwing, and all the objects of the array get constructed completely, than where does the need of the destructor call arise from?

The compiler can't fully reason about that if the constructor is non-trivial. What if the constructor calls a function in a different compilation unit that throws an exception? Member variable constructors throwing? Etc.

The throw() function decorator is not enough. In fact you should never use it. It's also a pessimisation.

Also what exactly is the difference in this context when we define a default constructor in our class vs a synthesized default constructor in our class?

It's not quite as clear cut as just a difference between user-defined and synthesized. There are subtleties at play. See: http://stackoverflow.com/a/3899237/2533196

g++ seems to accept the synthesized version whereas errors when one defines a default constructor(as Halfsan mentioned above), whereas clang rejects both the versions. Who is at fault here?

clang is being conservative, which is usually a Good Thing(tm). In this case, with a trivial default constructor, then clang should probably not be emitting an error. However, that's just my opinion and I'm sure there could be a good reason, that I'm not currently aware of, why it should still error out for all cases. I'll let the real experts decide on that though.

  - ½

Thanks David, Halfdan for your valuable inputs.
Just need some more clarification so that I get the complete thing.

Yes I understood that when we new[] an array of objects and the
constructor of one of the objects throws, the already constructed objects
should be destructed as the complete array construction could not get
through successfully.

But assume if the constructor is not throwing, and all the objects of the
array get constructed completely, than where does the need of the
destructor call arise from?

Is it the possibility of a runtime throw which prevents the compiler from
compiling a deleted destructor definition in case of an array allocation?

Precisely - whether or not an exception occurs is a runtime property, so
the compiler can't know whether it will happen and needs to emit the code
to handle it regardless.

Also what exactly is the difference in this context when we define a
default constructor in our class vs a synthesized default constructor in
our class?

It's possible that GCC is just being lazy, or that there's a requirement
that the default ctor is 'nothrow' then no dtors need be called.

g++ seems to accept the synthesized version whereas errors when one
defines a default constructor(as Halfsan mentioned above), whereas clang
rejects both the versions. Who is at fault here?

I'd have to go track down the wording. I don't know off hand.

Hi David, Halfdan,

Thanks a lot for your valuable inputs. Got caught up in some personal work so couldnt reply

before.

I tried tracking down the exact wordings to confirm the behaviour, but was of no luck.

We are basically putting a restriction on dynamic allocation of an array of objects by

marking the destructor of that class as deleted.

The standard says:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

Please if you could help nail down the exact behaviour with reference to the standard??

Would be of great help!

Thanks,

Rahul

I’m not sure this is quite the right wording (I was expecting something that used the term “odr-used”), but:

5.3.4p17 states “… If the new expression creates an array of objects of class type, access and ambiguity control are done for the destructor”

I think I got something relevant: Do these words make any sense in our context?

An allocation or deallocation function for a class is odr-used by a
new expression appearing in a potentially-evaluated expression as specified in 5.3.4(New) and 12.5(Free store). A deallocation function for a class is odr-used by a delete expression appearing in a potentially-evaluated expression as specified in 5.3.5(Delete) and 12.5(Free store).

What exactly does one mean by saying that something is odr-used?

I think I got something relevant: Do these words make any sense in our
context?

Not quite

An allocation or deallocation function

Allocation/deallocation function does not refer to
constructor/destructor. See 3.4p1:

"A C++ implementation provides access to, and management of, dynamic
storage via the global allocation functions operator new and operator
new[] and the global deallocation functions operator delete and
operator delete[]."

These are actually not the ctor/dtor, in 3.4p2 their declarations are shown as:

void* operator new(std::size_t);
void* operator new[](std::size_t);
void operator delete(void*);
void operator delete[](void*);

So, roughly, if you say "new T" first operator new(sizeof(T)) is
called, then the ctor for T is called on the returned storage, if the
T ctor throws, then operator delete is called on the storage before
the exception is propagated (that's why even non-array "new T"
odr-uses the deallocation function, operator delete - even though it
doesn't/shouldn't odr-use T's dtor).

for a class is odr-used by a
new expression appearing in a potentially-evaluated expression as specified
in 5.3.4(New) and 12.5(Free store). A deallocation function for a class is
odr-used by a delete expression appearing in a potentially-evaluated
expression as specified in 5.3.5(Delete) and 12.5(Free store).

What exactly does one mean by saying that something is odr-used?

The simplistic interpretation is that if something is odr-used it must
be defined (ie: there must be (one) definition). For example:

void f1();
void f2();

int main() {
  f1(); // odr use of f1 - the program is ill-formed if 'f1' is not defined
  size_t s = sizeof(f2); // non-odr-use of f2 - the program is valid
even if 'f2' is never defined
}

Thanks David for your valuable inputs.

Hi Richard,

Any comments on this behaviour??

Thanks,
Rahul

Thanks David for your valuable inputs.

David provided you with an approximation of the answer a few messages ago:

5.3.4p17 states "... If the new expression creates an array of objects of

class type, access and ambiguity control are done for the destructor"

This wording was somewhat defective, because it didn't deal with the case
of a deleted destructor (and also incorrectly suggests that a destructor
might be ambiguous). In the latest draft, this wording has been fixed, and
it now says:

5.3.4p19: "If the new-expression creates an array of objects of class type,
the destructor is potentially invoked (12.4)."
12.4p11: "A program is ill-formed if a destructor that is potentially
invoked is deleted or not accessible from the context of the invocation."

Hi Richard,

Thanks Richard.