Spurious C++ destructor call under Visual Studio 2008 (absent under GCC) - c++

Take the following (contrived) class hierarchy that prints to the console from the constructors and destructors:
#include <iostream>
class A {
public:
A() { std::cout << "A"; }
~A() { std::cout << "~A"; }
};
class B : public A {
public:
B() { std::cout << "B"; }
~B() { std::cout << "~B"; }
};
void func(A a) { }
int main() {
B b;
func(b);
std::cout << "X";
return 0;
}
Compiled under linux with gcc, it prints AB~AX~B~A as expected (the ~A printed before X is a result of the pass-by-value to func which creates a copy that is destructed when the function returns).
But compiled under windows with VS2008 it prints AB~A~AX~B~A - where does the extra ~A come from? It vanishes if the copy xtor is explicitly defined A(A& that) {}; or if the destructor is declared virtual (as it should be, arguably).

The comments suggest that MSVC 2008 uses a temporary that g++ doesn't, to pass the parameter. If so, this is a bug. From C++03 [dcl.init]/12:
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
Now here is the crucial bit. In T x = a;, if a is not a T or derived from T, then that is equivalent to T x = T(a);, and an extra temporary is conceptually used. (This temporary is eligible for copy-elision).
However, if a is a T or derived from T, then there must not be an extra temporary.It is the same as T x(a);.
In this question's code, since B derives from A, there must not be a temporary.
The supporting text in C++03 is under [dcl.init]/14 (I have highlighted the parts relevant to this question's code sample):
If the destination type is a (possibly cv-qualified) class type:
If the class is an aggregate (8.5.1), and the initializer is a brace-enclosed list, see 8.5.1.
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an imple-
mentation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

Related

Why is this direct initialization valid? (C++ 17)

Consider the following two classes:
class B
{
public:
B() { }
B(const B& b) = delete; //Move ctor not implicitly declared
};
class A
{
public:
A() { }
operator B()
{
return B();
}
};
I can see why this code compiles fine:
A a;
B b = a;
Following the rules of copy-initialization, the object "a" gets converted to a prvalue of type B and since in C++17 the copy constructor is not needed anymore there's no error:
If T is a class type, and the cv-unqualified version of the type of
other is not T or derived from T, or if T is non-class type, but the
type of other is a class type, user-defined conversion sequences that
can convert from the type of other to T (or to a type derived from T
if T is a class type and a conversion function is available) are
examined and the best one is selected through overload resolution. The
result of the conversion, which is a prvalue temporary (until
C++17)prvalue expression (since C++17) if a converting constructor was
used, is then used to direct-initialize the object. The last step is
usually optimized out and the result of the conversion is constructed
directly in the memory allocated for the target object, but the
appropriate constructor (move or copy) is required to be accessible
even though it's not used. (until C++17)
However why does this direct list-initialization compile too?
A a;
B b{ a };
I couldn't find any wording in the list-initialization stating the compiler should attempt to convert A into B in this case. Only that overload resolution on constructors is considered:
If the previous stage does not produce a match, all constructors of T
participate in overload resolution against the set of arguments that
consists of the elements of the braced-init-list, with the restriction
that only non-narrowing conversions are allowed
However in this case the copy constructor is deleted, so shouldn't it not be selected by overload resolution?
This is CWG 2327. You're correct as far as the standard goes, but some compilers additionally consider conversion functions in this context as well - because it really makes sense to.

I think there might be a small inaccuracy in bullet point (17.6.2) in §8.5[dcl.init]/17 in N4140

In bullet point (17.6.2) in §8.5[dcl.init]/17 in N4140 we have (emphasis is mine):
Otherwise (i.e., for the remaining copy-initialization cases),
user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
The portion of the text in bold seems to indicate that direct-initializations will never invoke a user-defined conversion sequence. But that's not what I found out below:
#include <iostream>
struct A {
A() { std::cout << "default ctor A" << '\n'; }
A(const A&) { std::cout << "copy A" << '\n'; }
};
struct C {
C() { std::cout << "default ctor C" << '\n'; }
operator A() { std::cout << "C::operator A()" << '\n'; return A(); };
};
int main()
{
C c;
A a{c}; // direct-initialization where the C::operator A() is invoked
// to copy construct the object `a`.
}
The following is printed by this snippet:
default ctor C
C::operator A()
default ctor A
copy A
copy A
See live example
Edit
In response to #Johannes answer, please consider A a(c); instead of A a{c};. The rest remains valid, as far as I can understand.
When discussing direct initialization (emphasis is my own):
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified
version of the source type is the same class as, or a derived class of, the class of the destination,
constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best
one is chosen through overload resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its argument(s). If no constructor
applies, or the overload resolution is ambiguous, the initialization is ill-formed.
This line leads to section §8.5 (2.8,2.9) on Overload Resolutions which describes the process.
Overload resolution selects the function to call in seven distinct contexts within the language:
...
(2.4) — invocation of a constructor for direct-initialization (8.5) of a class object...
But, once the candidate functions and argument lists have been identified, the selection of the best function
is the same in all cases:
(2.8) — First, a subset of the candidate functions (those that have the proper number of arguments and meet
certain other conditions) is selected to form a set of viable functions (13.3.2).
(2.9) — Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed
to match each argument to the corresponding parameter of each viable function.
Given the above, direct initialization does in fact go through the process of selecting candidate constructors (in your case, the one parameter constructor A::A( A const &) ) and finding an implicit converse sequence to match the two together - that is operator C::A().
It is somewhat misleading that they apply - what seems to be - redundant language in the text for other copy-initialization cases.
The extra language on an implicit conversion sequence - that you quote in the question - for the other copy-initialization cases of (17.6.2) directly refer to the following case:
class B {
operator A();
};
class A {
};
B b;
A a = b;
This isn't covered under the first point in (17.6.1) as B doesn't derive from A nor do any helpful constructors exist. Therefore it falls under the 'other' clause.
I don't understand your concern, but answering what happens, in case this can solve it for you. A a{c} directly branches off of bullet 1 of dcl.initp17 to dcl.init.list.
From there it happens that it will initialize a const A& from a C (by over.match.list selecting the copy-constructor of A). This will invoke the paragraph you cited (by first branching off to dcl.init.ref and then applying dcl.initp17 in turn). This will construct a normal user-defined conversion sequence which is able to use conversion functions, such as your operator A.
I think you are confused by the fact that it only mentions user defined conversion sequences for the copy initializations, but in the preceeding bullet it doesn't. The reason is that when doing direct initialization, that is conceptual a direct call to the constructors of a class instead of being a conversion (in which the call to constructors is only a part, surrounded by a standard conversion sequence before (for constructors) and after (for conversion functions/operators) it). In other words, for direct initialization, you already force the constructor call, rather than making the compiler find out that it needs to do it. And you haven't "paid" a user defined conversion sequence (you cannot nest user defined conversion sequences, so they are "precious") - the compiler can still apply a user defined conversion sequence for matching up the argument for the constructor.
The proper testcase for this would be A a(c) rather than A a{c} though, to prevent branching off to dcl.init.list.

Move constructor is required even if it is not used. Why?

Why?! Why C++ requires the class to be movable even if it's not used!
For example:
#include <iostream>
using namespace std;
struct A {
const int idx;
// It could not be compileld if I comment out the next line and uncomment
// the line after the next but the moving constructor is NOT called anyway!
A(A&& a) : idx(a.idx) { cout<<"Moving constructor with idx="<<idx<<endl; }
// A(A&& a) = delete;
A(const int i) : idx(i) { cout<<"Constructor with idx="<<i<<endl; }
~A() { cout<<"Destructor with idx="<<idx<<endl; }
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
The output is (the move constructor is not called!):
Constructor with idx=0
Constructor with idx=1
Destructor with idx=1
Destructor with idx=0
The code can not be compiled if moving constructor is deleted ('use of deleted function ‘A::A(A&&)’'. But if the constructor is not deleted it is not used!
What a stupid restriction?
Note:
Why do I need it for? The practical meaning appears when I am trying to initialize an array of objects contains unique_ptr field.
For example:
// The array of this class can not be initialized!
class B {
unique_ptr<int> ref;
public:
B(int* ptr) : ref(ptr)
{ }
}
// The next class can not be even compiled!
class C {
B arrayOfB[2] = { NULL, NULL };
}
And it gets even worse if you are trying to use a vector of unique_ptr's.
Okay. Thanks a lot to everybody. There is big confusion with all those copying/moving constructors and array initialization. Actually the question was about the situation when the compiler requires a copying consrtuctor, may use a moving construcor and uses none of them.
So I'm going to create a new question a little bit later when I get a normal keyboard. I'll provide the link here.
P.S. I've created more specific and clear question - welcome to discuss it!
A a[2] = { 0, 1 };
Conceptually, this creates two temporary A objects, A(0) and A(1), and moves or copies them to initialise the array a; so a move or copy constructor is required.
As an optimisation, the move or copy is allowed to be elided, which is why your program doesn't appear to use the move constructor. But there must still be a suitable constructor, even if its use is elided.
A a[2] = { 0, 1 };
This is aggregate initialization. §8.5.1 [dcl.init.aggr]/p2 of the standard provides that
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are 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.
§8.5 [dcl.init]/p16, in turn, describes the semantics of copy initialization of class type objects:
[...]
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated (13.3.1.3), and the best one is chosen through overload
resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its
argument(s). If no constructor applies, or the overload resolution is
ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
Since 0 and 1 are ints, not As, the copy initialization here falls under the second clause. The compiler find a user-defined conversion from int to A in your A::A(int) constructor, calls it to construct a temporary of type A, then performs direct initialization using that temporary. This, in turn, means the compiler is required to perform overload resolution for a constructor taking a temporary of type A, which selects your deleted move constructor, which renders the program ill-formed.

In copy-initialization, is the call to the copy constructor explicit or implicit?

class AAA {
public:
explicit AAA(const AAA&) {}
AAA(int) {}
};
int main() {
AAA a = 1;
return 0;
}
In the above code, as I understand, though elided in most cases, the copy constructor is still semantically required to be called. My question is, is the call explicit or implicit? For a long time I have the conclusion in my mind that the call to AAA::AAA(int) is implicit but the call to the copy constructor is not. Today I accidentally got g++ to compile the above code and it reported error. (VC12 compiles OK.)
In section 8.5 of the standard:
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated (13.3.1.3), and the best one is chosen through overload
resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its
argument(s). If no constructor applies, or the overload resolution is
ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
The bolded direct-initialize in the above quotes means the call to copy constructor is explicit, right? Is g++ wrong or my interpretation of the standard wrong?
Looks like this bug: g++ fails to call explicit constructors in the second step of copy initialization
g++ fails to compile the following code
struct X
{
X(int) {}
explicit X(X const &) {}
};
int main()
{
X x = 1; // error: no matching function for call to 'X::X(X)'
}
The second step of a copy initialization (see 8.5/16/6/2) is a
direct-initialization where explicit constructors shall be considered
as candidate functions.
Looks like copy constructor is never called . Only constructor is called . The following code may call copy constructor
AAA a = 1;
AAA ab = a;
Not sure why G++ is compiling it .

Compiler optimization of implicit constructor conversion

In the following code, I expect A's constructor is called, followed by A's copy constructor. However, It turns out only constructor is get called.
// MSVC++ 2008
class A
{
public:
A(int i):m_i(i)
{
cout << "constructor\n";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructor\n";
}
private:
int m_i;
};
int main()
{
// only A::A() is called
A a = 1;
return 0;
}
I guess the compiler is smart enough to optimize away the second call, to initialize the object a directly with the constructor. So is it a standard-defined behavior or just implementation-defined?
It's standard, but there's no optimization involved.
Actually, I believe there is an optimization involved, but it's still entirely standard.†
This code:
A a = 1;
invokes the converting constructor†† of A. A has a single converting constructor A(int i) that allows an implicit conversion from int to A.
If you prepend the constructor declaration with explicit, you'll find the code won't compile.
class A
{
public:
explicit A(int i) : m_i(i) // Note "explicit"
{
cout << "constructor\n";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructor\n";
}
private:
int m_i;
};
void TakeA(A a)
{
}
int main()
{
A a = 1; // Doesn't compile
A a(1); // Does compile
TakeA(1); // Doesn't compile
TakeA(A(1)); // Does compile
return 0;
}
† After looking at the standard again, I may have been initially wrong.
8.5 Initializers [dcl.init]
12. The initialization that occurs in argument passing, function
return, throwing an exception (15.1), handling an exception (15.3),
and brace-enclosed initializer lists (8.5.1) is called
copy-initialization and is equivalent to the form
T x = a;
14. The semantics of initializers are as follows. The destination
type is the type of the object or reference being initialized and the
source type is the type of the initializer expression. The source type
is not defined when the initializer is brace-enclosed or when it is a
parenthesized list of expressions.
...
If the destination type is a (possibly cv-qualified) class type:
If the class is an aggregate (8.5.1), and the initializer is a brace-enclosed list, see 8.5.1.
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
...
So in one sense it is very much an optimization. But I wouldn't worry about it since it is explicitly allowed by the standard and just about every compiler nowadays does the elison.
For a much more thorough treatment on initialization, see this GotW article (#36). The article seems to agree with the above interpretation of the standard:
NOTE: In the last case ("T t3 = u;") the compiler could call both the
user-defined conversion (to create a temporary object) and the T copy
constructor (to construct t3 from the temporary), or it could choose
to elide the temporary and construct t3 directly from u (which would
end up being equivalent to "T t3(u);"). Since July 1997 and in the
final draft standard, the compiler's latitude to elide temporary
objects has been restricted, but it is still allowed for this
optimization and for the return value optimization.
†† ISO/IEC 14882:2003 C++ Standard reference
12.3.1 Conversion by constructor [class.conv.ctor]
1. A constructor declared without the function-specifier explicit
that can be called with a single parameter specifies a conversion from
the type of its first parameter to the type of its class. Such a
constructor is called a converting constructor. [Example:
class X {
// ...
public:
X(int);
X(const char*, int =0);
};
void f(X arg)
{
X a = 1; // a = X(1)
X b = "Jessie"; // b = X("Jessie",0)
a = 2; // a = X(2)
f(3); // f(X(3))
}
—end example]
2. An explicit constructor constructs objects just like non-explicit
constructors, but does so only where the direct-initialization syntax
(8.5) or where casts (5.2.9, 5.4) are explicitly used. A default
constructor may be an explicit constructor; such a constructor will be
used to perform default-initialization or valueinitialization (8.5).
[Example:
class Z {
public:
explicit Z();
explicit Z(int);
// ...
};
Z a; // OK: default-initialization performed
Z a1 = 1; // error: no implicit conversion
Z a3 = Z(1); // OK: direct initialization syntax used
Z a2(1); // OK: direct initialization syntax used
Z* p = new Z(1); // OK: direct initialization syntax used
Z a4 = (Z)1; // OK: explicit cast used
Z a5 = static_cast<Z>(1); // OK: explicit cast used
—end example]
No optimization here. When = is used in the initialization, it is eqivalent(nearly) to calling the constructor with the right hand side as an argument. So this:
A a = 1;
Is (mostly) equivalent to this:
A a(1);