What's the compiler's freedom for destructor elision? - c++

It's well known that, under certain conditions, the compiler might elide calls to the copy constructor. However, the Standard is clear saying that the compiler only has the freedom to change the runtime behavior (calling or not the copy constructor) but translation is performed as if the copy constructor is called. In particular, the compiler checks whether there's a valid copy constructor to call.
I came across a situation where a destructor call might be elided but compilers differ on whether a valid destructor needs to exist or not.
Here is a complete example showing how this issue might occur and how compilers differ in behavior.
template <typename T>
struct A {
~A() { (void) sizeof(T); }
};
struct B; // defined elsewhere.
struct C {
A<B> x, y;
~C(); // defined in a TU where B is complete.
};
int main() {
C c;
}
When compiling main() the compiler generates C's default constructor. This constructor default initializes first x and then y. If an exception is thrown during y construction, then x must be destroyed. The generated code looks like this:
new ((void*) &this->x) A<B>; // default initializes this->x.
try {
new ((void*) &this->y) A<B>; // default initializes this->y.
}
catch (...) {
(this->x).~A<B>(); // destroys this->x.
throw;
}
Knowing that A<B>'s default constructor is trivial (and doesn't throw), under the as-if rule, the compiler might simplify the code to:
new ((void*) &this->x) A<B>; // default initializes this->x.
new ((void*) &this->y) A<B>; // default initializes this->y.
Therefore, there's no need to call ~A<B>(). (Actually, the compiler can even remove the two initializations above since the A<B>'s constructor is trivial, but this is not important to this discussion.)
The question is: Even though the call to the destructor might be elided, should the compiler verify whether a valid destructor is available? I couldn't find anything on the Standard that clarifies the matter. Can anyone provide relevant quotes?
If the compiler decides to not translate ~A<B>() (like gcc and Visual Studio do) then the compilation succeeds.
However, if the compiler decides to translate ~A<B>() anyway (like clang and icc do), then it raises an error because here B is an incomplete type and its size cannot be taken.

I don't think this is specified by the standard. If ~A<B> is instantiated then it is ill-formed and a diagnostic is required. And as you say if constructing y throws, then x must be destroyed.
However, constructing y can never throw, so arguably there will never be a requirement for the destructor's definition to exist (15.2/2, 14.7.1/3).

Related

Why in this situation only one constructor is called but two destructors are called?

In the first time,the code looks like below:
#include "stdafx.h"
#include<iostream>
using namespace std;
class Test{
public:
explicit Test(int);
~Test();
//Test(Test&);
int varInt;
};
Test::Test(int temp){
varInt = temp;
cout << "call Test::constructor\n";
}
Test::~Test(){
cout << "call Test::destructor\n";
}
/*Test::Test(Test&temp){
varInt = temp.varInt;
cout << "call Test::copy constructor\n";
}*/
void func(Test temp){
cout << "call func\n";
}
int _tmain(int argc, _TCHAR* argv[])
{
func(Test(1));
return 0;
}
output:
call Test::constructor
call func
call Test::destructor
call Test::destructor
This confuses me,cause there's only one object that was created(as the argument of func),but two destructors were called after the function ends.
I started to wonder,is this because the default copy constructor was called?So I wrote the definition of copy constructor,which made things just more strange.
After I add the Commented-Out Code as you can see above,namely the definition of copy constructor,into the class,the output became like this:
output:
call Test::constructor
call func
call Test::destructor
Things became just right now.
Can someone explain this phenomenon to me?Thank u very much.
Your interpretation of your original code (that the implicitly-declared copy constructor is being called) is correct.
Depending on the version of the standard that your compiler is implementing, it may actually be using the implicitly-declared move constructor instead. But this amounts to the same thing.
Your modified code (where you've explicitly provided a copy constructor) happens to be triggering the copy elision optimization, where the compiler just constructs the object in the desired location to begin with. This is one of the few situations where the standard specifically allows an optimization even though it affects the observable behavior of the program (since you can tell whether your copy constructor was called).
Nothing about your modified code requires copy elision, and nothing about your original code forbids it; the two versions just happen to differ in whether they trigger the optimization in your compiler under your current settings.
Note: the situation here changes a bit in C++17, where this optimization does become mandatory in some cases. See my above link for details.
Edited to add: Incidentally, in your version with an explicit copy constructor, your constructor is unusual in taking a non-constant reference. This actually means that it can't be used anyway, since a non-constant reference can't bind to the temporary Test(1). I think this oddness may have to do with why your compiler is performing copy elision. If you change the constructor to take a constant reference, as the implicitly-declared copy constructor would, you may see the behavior you were expecting, with your explicit copy constructor being called and the destructor being called twice. (But that's just speculation on my part; you'll have to try it and see!)
You have two objects of class Test. Since you pass arguments by value, one is constructed explicitly in the main function, another one is constructed with default copy constructor, as your copy constructor is commented out. Both objects get destructed. One on the exit from func(), another at the exit from main(). Hence two destructor calls.

Does the C++ standard guarantee that a function return value has a constant address?

Consider this program:
#include <stdio.h>
struct S {
S() { print(); }
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
As far as I can tell, there is exactly one S object constructed here. There is no copy elision taking place: there is no copy to be elided in the first place, and indeed, if I explicitly delete the copy and/or move constructor, compilers continue to accept the program.
However, I see two different pointer values printed. This happens because my platform's ABI returns trivially copyable types such as this one in CPU registers, so there is no way with that ABI of avoiding a copy. clang preserves this behaviour even when optimising away the function call altogether. If I give S a non-trivial copy constructor, even if it's inaccessible, then I do see the same value printed twice.
The initial call to print() happens during construction, which is before the start of the object's lifetime, but using this inside a constructor is normally valid so long as it isn't used in a way that requires the construction to have finished -- no casting to a derived class, for instance -- and as far as I know, printing or storing its value doesn't require the construction to have finished.
Does the standard allow this program to print two different pointer values?
Note: I'm aware that the standard allows this program to print two different representations of the same pointer value, and technically, I haven't ruled that out. I could create a different program that avoids comparing pointer representations, but it would be more difficult to understand, so I would like to avoid that if possible.
T.C. pointed out in the comments that this is a defect in the standard. It's core language issue 1590. It's a subtly different issue than my example, but the same root cause:
Some ABIs require that an object of certain class types be passed in a register [...]. The Standard should be changed to permit this usage.
The current suggested wording would cover this by adding a new rule to the standard:
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. [...]
For the most part, this would permit the current GCC/clang behaviour.
There is a small corner case: currently, when a type has only a deleted copy or move constructor that would be trivial if defaulted, by the current rules of the standard, that constructor is still trivial if deleted:
12.8 Copying and moving class objects [class.copy]
12 A copy/move constructor for class X is trivial if it is not user-provided [...]
A deleted copy constructor is not user-provided, and nothing of what follows would render such a copy constructor non-trivial. So as specified by the standard, such a constructor is trivial, and as specified by my platform's ABI, because of the trivial constructor, GCC and clang create an extra copy in that case too. A one-line addition to my test program demonstrates this:
#include <stdio.h>
struct S {
S() { print(); }
S(const S &) = delete;
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
This prints two different addresses with both GCC and clang, even though even the proposed resolution would require the same address to be printed twice. This appears to suggest that while we will get an update to the standard to not require a radically incompatible ABI, we will still need to get an update to the ABI to handle the corner case in a manner compatible with what the standard will require.
This is not an answer, rather a note on the different behavior of g++ and clang in this case, depending on the -O optimization flag.
Consider the following code:
#include <stdio.h>
struct S {
int i;
S(int _i): i(_i) {
int* p = print("from ctor");
printf("about to put 5 in %p\n", (void *)&i);
*p = 5;
}
int* print(const char* s) {
printf("%s: %p %d %p\n", s, (void *) this, i, (void *)&i);
return &i;
}
};
S f() { return {3}; }
int main() {
f().print("from main");
}
We can see that clang (3.8) and g++ (6.1) are taking it a bit differently, but both get to the right answer.
clang (for no -O, -O1, -O2) and g++ (for no -O, -O1)
from ctor: 0x7fff9d5e86b8 3 0x7fff9d5e86b8
about to put 5 in 0x7fff9d5e86b8
from main: 0x7fff9d5e86b0 5 0x7fff9d5e86b0
g++ (for -O2)
from ctor: 0x7fff52a36010 3 0x7fff52a36010
about to put 5 in 0x7fff52a36010
from main: 0x7fff52a36010 5 0x7fff52a36010
It seems that they both do it right in both cases - when they decide to skip the register optimization (g++ -O2) and when they go with the register optimization but copy the value to the actual i on time (all other cases).

Autogenerated move constructors causing illegal behavior

I asked a question about move constructors for which I haven't accepted an answer yet because I'm feeling more confused about certain aspects of the question even as I'm starting to get a grip on others. In particular, I've found a surprising case in which both g++ and clang++ generate incorrect move-constructors.
Question summary
g++ and clang++ apparently violate the rule that move-constructors are not generated when destructors are explicitly defined; why? Is this a bug, or am I misunderstanding what's going on?
For correctness, these (possibly-illegal) move constructors should invalidate RHS pointer members, but they don't. Why not?
It appears that the only way to avoid the unwanted behavior is to explicitly define a correct move constructor for every class that uses delete in its destructor. Does the Qt library (version 5.4) do this?
Part 1: Illegally auto-generated constructors?
Consider the following code:
class NoMove
{
public:
~NoMove() {}
};
int main()
{
std::cout << "NoMove move-constructible? " <<
std::is_move_constructible<NoMove>::value << std::endl;
}
Compiled with both g++ 4.9.2 and clang++ 3.5.1, this code prints:
NoMove move-constructible? 1
...But since NoMove has an explicitly defined destructor, I would expect that neither a move constructor nor a copy constructor should be auto-generated. Note that the unexpected constructor generation is not due to the fact that the destructor is trivial; I get the same behavior when the destructor delete[]s an array (!!), and I am even able to compile code that requires a valid move constructor (!!!!!). (See example below.) What's going on here? Is it legal to auto-generate a move constructor here, and if so, why?
Part 2: (Possibly illegal) auto-generated constructors causing undefined behavior?
It appears that providing safe move constructors when delete is involved is fairly simple, but I just want to make sure I understand: when a class contains a pointer member and owns the underlying data, is there any case in which it wouldn't be correct and sufficient for the move constructor to invalidate the RHS pointer after setting the destination pointer to the old value?
Consider the following example, which is similar to the NoMove example above and is based on my original question:
class DataType
{
public:
DataType()
{
val = new int[35];
}
~DataType()
{
delete[] val;
}
private:
int* val;
};
class Marshaller
{
public:
Marshaller()=default;
DataType toDataType() &&
{
return std::move(data);
}
private:
DataType data;
};
void DoMarshalling()
{
Marshaller marshaller;
// ... do some marshalling...
DataType marshalled_data{std::move(marshaller).toDataType()};
}
This compiles just fine--showing that, yes, DataType has an auto-generated move constructor. And of course, when run, it causes a double-deletion error.
Now, this would be okay, if the auto-generated move constructor invalidated the RHS pointer. So, if it's okay to auto-generate a move constructor here, why isn't that done safely? The move constructor that makes this work is simply:
DataType(DataType&& rhs) :
val{rhs.val}
{
rhs.val = nullptr;
}
(Right? Am I missing anything? Should it perhaps be val{std::move(rhs.val)}?)
This seems like it would be a perfectly safe function to auto-generate; the compiler knows that rhs is an r-value because the function prototype says so, and therefore it's entirely acceptable to modify it. So even if DataType's destructor didn't delete[] val, it seems like there wouldn't be any reason not to invalidate rhs in the auto-generated version, except, I suppose, for the fact that this leads to a trivial performance hit.
So if the compiler is auto-generating this method--which, again, it shouldn't, especially since we can just as easily get this exact behavior from standard library code using unique_ptr-- why is it auto-generating it incorrectly?
Part 3: Avoiding this behavior in Qt (especially QByteArray in Qt 5.4)
Finally, a (hopefully) easy question: do Qt 5.4's heap-allocating classes such as QByteArray (which is what I'm actually using as the DataType in my original question) have correctly implemented move constructors, invalidating any moved-from owning pointer(s)?
I wouldn't even bother to ask, because Qt seems pretty solid and I haven't seen any double-deletion errors yet, but given that I was taken off guard by these incorrect compiler-generated move constructors, I'm concerned that it's quite easy to end up with incorrect move constructors in an otherwise-well-implemented library.
Relatedly, what about Qt libraries written before C++11 that don't have explicit move-constructors? If I can accidentally coerce an auto-generated move constructor that behaves erroneously in this case, does anyone know if compiling, say, Qt 3 with a C++11-compliant compiler causes undefined destruction behavior in use-cases like this?
The problem is that you are confusing is_move_constructible and "has a move constructor". is_move_constructible<T> doesn't test whether T has a move constructor. It tests whether T can be constructed from an rvalue of type T. And const T& can bind to a T rvalue.
What you are seeing is the autogenerated copy constructor T(const T&) doing its work - and failing miserably.
I would expect that neither a move constructor nor a copy constructor should be auto-generated.
Your link talks about the move constructor. It doesn't talk about the copy constructor, which is always implicitly declared if you don't declare it.
Now, if you declared a move operation, the implicitly declared copy constructor would be defined as deleted, but you didn't do that, so it's defined as defaulted and performs a memberwise copy. [class.copy]/p7:
If the class definition does not explicitly declare a copy
constructor, one is declared implicitly. If the class definition
declares a move constructor or move assignment operator, the
implicitly declared copy constructor is defined as deleted; otherwise,
it is defined as defaulted (8.4). The latter case is deprecated if the
class has a user-declared copy assignment operator or a user-declared
destructor.

Does copy-constructor or constructor work when we do copy initialization

#include <iostream>
using namespace std;
class ExClass
{
int data;
ExClass(const ExClass&);
public:
ExClass() : data(0) {}
ExClass(int d) : data(d) { cout<<"Constructor"<<endl; }
};
int main()
{
ExClass var(2);
ExClass var2=2;
return 0;
}
To test whether it calls copy-constructor or constructor when I use copy-initialization, I made copy-constructor private. Although it works with visual c++ 2005, codeblocks 13.12 (compiling with C++11 standards) gives an error.
When I run it as it is, it gives:
Constructor
Constructor
as an input.
Am I correct thinking that it means var(2) and var2=2 have the same meaning and they both call the same constructor?
If it is, why codeblocks gives an error? Since it doesn't use copy-constructor, it should not give an error.
This...
ExClass var2=2;
...is equivalent to this...
ExClass var2 = ExClass(2);
...which nominally invokes the copy-constructor, but the Standard has a special provision allowing for this to be elided into direct construction of var2. That's an optional optimisation the compiler can choose to perform - only if the compiler doesn't elide will the missing definition for the copy constructor matter. Either way though, a the compiler must check that a copy-construction would be a legal operation (e.g. it's not deleted).
So, both compilers are right, and indeed the same compiler may get an error or not depending on the command-line optimisation flags it's invoked with.
ExClass var2 = 2;
converts 2 to an ExClass temporary (prvalue), then initializes var2 with that prvalue. This second step involves either a move- or a copy-constructor (typically). The call to that copy/move constructor can be elided, but it has to be possible/valid. (If the call is elided, the copy/move ctor is not odr-used and therefore no definition is required.)
The Standardese can be found in [dcl.init]/17 and [class.copy]/31ff

Strange behavior of copy-initialization, doesn't call the copy-constructor!

I was reading the difference between direct-initialization and copy-initialization (§8.5/12):
T x(a); //direct-initialization
T y = a; //copy-initialization
What I understand from reading about copy-initialization is that it needs accessible & non-explicit copy-constructor, or else the program wouldn't compile. I verified it by writing the following code:
struct A
{
int i;
A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
private:
A(const A &a) { std::cout << " A(const A &)" << std::endl; }
};
int main() {
A a = 10; //error - copy-ctor is private!
}
GCC gives an error (ideone) saying:
prog.cpp:8: error: ‘A::A(const A&)’ is private
So far everything is fine, reaffirming what Herb Sutter says,
Copy initialization means the object is initialized using the copy constructor, after first calling a user-defined conversion if necessary, and is equivalent to the form "T t = u;":
After that I made the copy-ctor accessible by commenting the private keyword. Now, naturally I would expect the following to get printed:
A(const A&)
But to my surprise, it prints this instead (ideone):
A(int i)
Why?
Alright, I understand that first a temporary object of type A is created out of 10 which is int type, by using A(int i), applying the conversion rule as its needed here (§8.5/14), and then it was supposed to call copy-ctor to initialize a. But it didn't. Why?
If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? After all, its not calling it. Its like a spoiled kid who first irritatingly asks for a specific toy, and when you give him one, the specific one, he throws it away, behind your back. :|
Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?
Are you asking why the compiler does the access check? 12.8/14 in C++03:
A program is ill-formed if the copy
constructor or the copy assignment
operator for an object is implicitly
used and the special member function
is not accessible
When the implementation "omits the copy construction" (permitted by 12.8/15), I don't believe this means that the copy ctor is no longer "implicitly used", it just isn't executed.
Or are you asking why the standard says that? If copy elision were an exception to this rule about the access check, your program would be well-formed in implementations that successfully perform the elision, but ill-formed in implementations that don't.
I'm pretty sure the authors would consider this a Bad Thing. Certainly it's easier to write portable code this way -- the compiler tells you if you write code that attempts to copy a non-copyable object, even if the copy happens to be elided in your implementation. I suspect that it could also inconvenience implementers to figure out whether the optimization will be successful before checking access (or to defer the access check until after the optimization is attempted), although I have no idea whether that warranted consideration.
Could this behavior be dangerous? I
mean, I might do some other useful
thing in the copy-ctor, but if it
doesn't call it, then does it not
alter the behavior of the program?
Of course it could be dangerous - side-effects in copy constructors occur if and only if the object is actually copied, and you should design them accordingly: the standard says copies can be elided, so don't put code in a copy constructor unless you're happy for it to be elided under the conditions defined in 12.8/15:
MyObject(const MyObject &other) {
std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
std::cout << "object returned from function\n"; // dangerous: if the copy is
// elided then an object will be returned but you won't see the message.
}
C++ explicitly allows several optimizations involving the copy constructor that actually change the semantics of the program. (This is in contrast with most optimizations, which do not affect the semantics of the program). In particular, there are several cases where the compiler is allowed to re-use an existing object, rather than copying one, if it knows that the existing object will become unreachable. This (copy construction) is one such case; another similar case is the "return value optimization" (RVO), where if you declare the variable that holds the return value of a function, then C++ can choose to allocate that on the frame of the caller, so that it doesn't need to copy it back to the caller when the function completes.
In general, in C++, you are playing with fire if you define a copy constructor that has side effects or does anything other than just copying.
In any compiler, syntax [and semantic] analysis process are done prior to the code optimization process.
The code must be syntactically valid otherwise it won't even compile. Its only in the later phase (i.e code optimization) that the compiler decides to elide the temporary that it creates.
So you need an accessible copy c-tor.
Here you can find this (with your comment ;)):
[the standard] also says that the temporary copy
can be elided, but the semantic
constraints (eg. accessibility) of the
copy constructor still have to be
checked.
RVO and NRVO, buddy. Perfectly good case of copy ellision.
This is an optimization by the compiler.
In evaluating: A a = 10; instead of:
first constructing a temporary object through A(int);
constructing a through the copy constructor and passing in the temporary;
the compiler will simply construct a using A(int).