Why does the compiler create a variable that's only used once? - c++

Consider the following two examples, both compiled with g++ (GCC) 12.1.0 targetting x86_64-pc-linux-gnu: g++ -O3 -std=gnu++20 main.cpp.
Example 1:
#include <iostream>
class Foo {
public:
Foo(){ std::cout << "Foo constructor" << std::endl;}
Foo(Foo&& f) { std::cout << "Foo move constructor" << std::endl;}
Foo(const Foo& f) {std::cout << "Foo copy constructor" << std::endl;}
~Foo() { std::cout << "Foo destructor" << std::endl;}
};
class Baz {
private:
Foo foo;
public:
Baz(Foo foo) : foo(std::move(foo)) {}
~Baz() { std::cout << "Baz destructor" << std::endl;}
};
Foo bar() {
return Foo();
}
int main() {
Baz baz(bar());
return 0;
}
Output:
Foo constructor
Foo move constructor
Foo destructor
Baz destructor
Foo destructor
Example 2:
#include <iostream>
class Foo {
public:
Foo(){ std::cout << "Foo constructor" << std::endl;}
Foo(Foo&& f) { std::cout << "Foo move constructor" << std::endl;}
Foo(const Foo& f) {std::cout << "Foo copy constructor" << std::endl;}
~Foo() { std::cout << "Foo destructor" << std::endl;}
};
class Baz {
private:
Foo foo;
public:
Baz(Foo foo) : foo(std::move(foo)) {}
~Baz() { std::cout << "Baz destructor" << std::endl;}
};
Foo bar() {
return Foo();
}
int main() {
// DIFFERENCE IS RIGHT HERE
Foo f = bar();
Baz baz(f);
return 0;
}
Output:
Foo constructor
Foo copy constructor
Foo move constructor
Foo destructor
Baz destructor
Foo destructor
Foo destructor
So the difference here is that there is an additional instance of Foo, which needs to be handled. I find this difference quite shocking, as my (apparently untrained) eyes would not even consider these pieces of code effectively different. Moreover, a collegue might ask me to turn example 1 into example 2 for readability and suddenly I will be incurring a performance penalty!
So why are example 1 and 2 so different according to the compiler/C++ spec that the resulting code suddenly needs to copy? Why can the compiler not generate the same code for both cases?

In
Foo f = bar();
Baz baz(f);
return 0
It is indeed obvious that f will never be used again and the compiler probably even works this out. However the compiler isn't allowed to move f into baz, all optimisations done by the compiler must result in your program having the same behaviour (if the program is well defined to start with). Especially in your case where the move and copy constructors print different things, making this optimisation would change the behaviour of your program. In a more realistic program the compiler may be able to deduce that the code would have the same behaviour with a move or a copy and change a copy into a move but it's always best to give the compiler as much help as possible and not rely on it making optimisations.
If you know that you don't need f any more then you can tell the compiler this by converting it to an rvalue reference using std::move, then it will use the move constructor (if available):
Foo f = bar();
Baz baz(std::move(f));
return 0

why are example 1 and 2 so different according to the compiler/C++ spec that the resulting code suddenly needs to copy?
Because in example 2 you have a variable named f of type Foo which is used to initialize baz while in example 1 you're initializing baz with a prvalue of type Foo which will result in directly constructing the parameter foo and then moving from it.
To be more precise, in example 2 since f in the statement Baz baz(f); is an lvalue expression and so the copy ctor Foo::Foo(const Foo&) will be used to initialize the parameter Foo foo of the converting ctor Baz::Baz(Foo). Thus we get the output corresponding to the copy ctor. Next due to foo(std::move(foo)) we will also get a call to the move ctor.
While in example 1, bar() is a prvalue of type Foo. This means that the parameter Foo foo of Baz::Baz(Foo) will be directly constructed(due to mandatory copy elison in C++17) and then due to foo(std::move(foo)) we also get a call to the move ctor.
Let's see step by step explanation.
Case 1
Here we consider example:
Baz baz(bar());
Step 1: The parameterized ctor Baz::Baz(Foo foo) will be used here.
Step 2: Since bar() is a prvalue, the parameter named foo of the parameterized ctor Baz::Baz(Foo foo) will be directly constructed due to mandatory copy elison in C++17. Thus we get the first statement of the output Foo constructor.
Step 3 Now due to foo(std::move(foo)) we get the call to the move ctor Foo::Foo(Foo&& f) and so the second statement of the output Foo move constructor.
Case 2
Here we consider:
Foo f = bar(); //this will call the default ctor and so we get the first statement of the output
//------v------->f is an lvalue
Baz baz(f);
Step 1 Here also the parameterized ctor Baz::Baz(Foo foo) will be used.
Step 2 But this time since f is an lvalue expression the copy ctor Foo::Foo(const Foo&) will be used to initialized the parameter named foo of the parameterized ctor Baz::Baz(Foo foo). Thus we get the second statement of the output Foo copy constructor.
Step 3 Next due to foo(std::move(foo)) we get a call to the move ctor and the third statement of the output Foo move constructor

Related

Different results between clang/gcc and MSVC for templated constructor in base class

I stumbled over the following piece of code. The "DerivedFoo" case produces different results on MSVC than on clang or gcc. Namely, clang 13 and gcc 11.2 call the copy constructor of Foo while MSVC v19.29 calls the templated constructor. I am using C++17.
Considering the non-derived case ("Foo") where all compilers agree to call the templated constructor, I think that this is a bug in clang and gcc and that MSVC is correct? Or am I interpreting things wrong and clang/gcc are correct? Can anyone shed some light on what might be going on?
Code (https://godbolt.org/z/bbjasrraj):
#include <iostream>
using namespace std;
struct Foo {
Foo() {
cout << "\tFoo default constructor\n";
}
Foo(Foo const &) { cout << "\tFoo COPY constructor\n";
}
Foo(Foo &&) {
cout << "\tFoo move constructor\n";
}
template <class U>
Foo(U &&) {
cout << "\tFoo TEMPLATED constructor\n";
}
};
struct DerivedFoo : Foo {
using Foo::Foo;
};
int main() {
cout << "Foo:\n";
Foo f1;
Foo f2(f1);
cout << "\nConst Foo:\n";
Foo const cf1;
Foo cf2(cf1);
cout << "\nDerivedFoo:\n";
DerivedFoo d1;
DerivedFoo d2(d1);
cout << "\nConst DerivedFoo:\n";
DerivedFoo const cd1;
DerivedFoo cd2(cd1);
}
Result for clang and gcc:
Foo:
Foo default constructor
Foo TEMPLATED constructor
Const Foo:
Foo default constructor
Foo COPY constructor
DerivedFoo:
Foo default constructor
Foo COPY constructor <<<<< This is different
Const DerivedFoo:
Foo default constructor
Foo COPY constructor
Result for MSVC:
Foo:
Foo default constructor
Foo TEMPLATED constructor
Const Foo:
Foo default constructor
Foo COPY constructor
DerivedFoo:
Foo default constructor
Foo TEMPLATED constructor <<<<< This is different
Const DerivedFoo:
Foo default constructor
Foo COPY constructor
It is correct that the constructor template is generally a better match for the constructor call with argument of type DerivedFoo& or Foo& than the copy constructors are, since it doesn't require a const conversion.
However, [over.match.funcs.general]/8 essentially (almost) says, in more general wording, that an inherited constructor that would have the form of a move or copy constructor is excluded from overload resolution, even if it is instantiated from a constructor template. Therefore the template constructor will not be considered.
Therefore the implicit copy constructor of DerivedFoo will be chosen by overload resolution for
DerivedFoo d2(d1);
and this will call Foo(Foo const &); to construct the base class subobject.
This wording is a consequence of CWG 2356, which was resolved after C++17, but I think it is supposed to be a defect report against older versions as well. (I don't really know though.)
So GCC and Clang are correct here. Also note that MSVC behaves according to the defect report as well since version 19.30 if the conformance mode (/permissive-) is used.

Should args to inherited constructors be copied when invoking the base ctor or not?

For the following program:
#include <iostream>
struct Foo
{
Foo() { std::cout << "Foo()\n"; }
Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
~Foo() { std::cout << "~Foo()\n"; }
};
struct A
{
A(Foo) {}
};
struct B : A
{
using A::A;
};
int main()
{
Foo f;
B b(f);
}
GCC gives:
$ g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Foo()
Foo(const Foo&)
~Foo()
~Foo()
VS 2017 (also in C++17 mode) gives:
Foo()
Foo(const Foo&)
Foo(const Foo&)
~Foo()
~Foo()
~Foo()
Who's right, and why?
(Let's also not forget that VS 2017 doesn't do mandated copy elision properly. So it could just be that the copy is "real" but GCC elides it per C++17 rules where VS doesn't...)
It appears that Visual Studio doesn't implement P0136 yet. The correct C++17 behavior is a single copy, the original C++14 behavior was two copies.
The C++14 rules (N4140:[class.inhctor]) would interpret:
struct B : A
{
using A::A;
};
as:
struct B : A
{
B(Foo f) : A(f) { }
};
The introduced constructors are speciifed in p3, the mem-initializer equivalence in p8. Hence you get two copies of Foo: one into B's synthesized constructor and one into A's real constructor.
The C++17 rules, as a result of P0136, are very different (N4659:[class.inhtor.init]): there, we directly invoke A's constructor. It's not like we're adding a new constructor to B anymore - and it's not a mechanism that's otherwise expressible in the language. And because we're directly invoking A(Foo), that's just the one single copy instead of two.
Elision notwithstanding, it looks to me like Visual Studio is wrong:
[C++17: class.inhctor.init]/1: When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited ([namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the D object.

Order of static destructors

If a class Foo has a static member variable Bar, I would expect Bar's destructor to run only after the last instance of Foo's destructor runs. This doesn't happen with the code snippet below (gcc 6.3, clang 3.8):
#include <memory>
#include <iostream>
class Foo;
static std::unique_ptr<Foo> foo;
struct Bar {
Bar() {
std::cout << "Bar()" << std::endl;
}
~Bar() {
std::cout << "~Bar()" << std::endl;
}
};
struct Foo {
Foo() {
std::cout << "Foo()" << std::endl;
}
~Foo() {
std::cout << "~Foo()" << std::endl;
}
static Bar bar;
};
Bar Foo::bar;
int main(int argc, char **argv) {
foo = std::make_unique<Foo>();
}
Outputs:
Bar()
Foo()
~Bar()
~Foo()
Why is the order of destruction not the reverse of construction here?
If ~Foo() uses Foo::bar this is a use after delete.
In C++ the objects are constructed in order of occurrence and destructed in the reverse order. First comes foo construction, then bar construction then main is executed then bar is destructed and then foo. This is the behavior you are seeing. The switch appears because the constructor of foo doesn't construct a Foo, it constructs an empty unique_ptr, so you don't get to see Foo() in the output. Then bar is constructed with the output and in main you create the actual Foo after foo has long been constructed.
I would expect Bar's destructor to run only after the last instance of Foo's destructor runs.
No, as a static data member, Foo::bar is independent of any instances of Foo.
And for the code you showed,
static std::unique_ptr<Foo> foo; // no Foo created here
Bar Foo::bar; // Foo::bar is initialized before main(), => "Bar()"
int main(int argc, char **argv) {
foo = std::make_unique<Foo>(); // an instance of Foo is created, => "Foo()"
}
// objects are destroyed in the reverse order how they're declared
// Foo::bar is defined after foo, so it's destroyed at first => "~Bar()"
// foo is destroyed; the instance of Foo managed by it is destroyed too => "~Foo()"
The complication here is that the code doesn’t instrument the constructor of foo. What happens is that foo gets constructed first, than Foo::bar gets constructed. The call to make…unique constructs a Foo object. Then main exits, and the two static objects get destroyed in reverse order of their construction: Foo::bar gets destroyed, then foo. The destructor for foo destroys the Foo object that it points to, which is the one created inmain.
Static objects' lifetimes are based solely on the order of their definition. The compiler doesn't "know enough" when to call Bar::Bar() as much as calling Bar::~Bar().
To illustrate the problem better, consider this
class Foo;
struct Bar {
Bar() {
std::cout << "Bar()" << std::endl;
}
~Bar() {
std::cout << "~Bar()" << std::endl;
}
void baz() {}
};
struct Foo {
Foo() {
bar.baz();
std::cout << "Foo()" << std::endl;
}
~Foo() {
std::cout << "~Foo()" << std::endl;
}
static Bar bar;
};
Foo foo;
Bar Foo::bar;
int main() {}
Prints
Foo()
Bar()
~Bar()
~Foo()
The addition of std::unique_ptr postpones Foo::Foo() after its construction in main, giving the illusion of the compiler "knowing" when to call Bar::Bar().
TLDR Static objects should be defined later than its dependencies. Before defining bar, it is just as much a bug to define a std::unique_ptr<Foo> and to define a Foo
In general, you should not write code which depends on the order of construction or destruction of static (or global) data. This makes unreadable and unmaintainable code (you might prefer static smart pointers, or
explicit initialization or startup routines called from main). And AFAIK that order is not specified when you link several translation units.
Notice that GCC provides the init_priority and constructor (with priority) attributes. I believe you should rather avoid using them. However, the __attribute__(constructor) is useful inside plugins, for plugin initialization.
In some cases, you might also use atexit(3) (at least on POSIX systems). I don't know if such registered functions are called before or after destructors (and I think you should not care about that order).

initialization ignores constructor templates

While pursuing some errors, I stumbled upon the following behavior of initialization, which seems odd to me: While initialization checks for existing constructors, there seem to be cases were templates for fitting constructors are ignored. Consider for example the following program:
#include <iostream>
template<class T>
struct A {
A() {};
template<class S>
A(const A<S>& a) {std::cout << "constructor template used for A" << std::endl;};
};
template<class T>
struct B{
B() {};
B(const B<int>& b) {std::cout << "constructor used for B" << std::endl;};
};
int main() {
A<int> a;
B<int> b;
A<int> aa = a;
B<int> bb = b;
A<double> aaa = a;
}
For me, this produces the output
constructor used for B
constructor template used for A
this means it does not use the constructor in the third line of main. Why not? Is there a reason? Or is my syntax off somewhere? The template seems to work, as it is successfully used in the last line.
I know the example seems overly complicated, but various simplifications made the behavior I wanted to display go away. Also: A template specialization will be used by the initialization and is how I currently prevent this causing errors (where it caused errors in the first place).
I am sorry if my question is off in any way, I am not a programmer, I am not a native speaker and this is my first question, please forgive me.
The compiler provides an implicitly declared non-template copy constructor with signature equivalent to
A(const A& a);
because a template constructor is not considered as a user defined copy constructor, i.e. a copy constructor has to be a non-template.
The implicitly declared copy constructor is a better match in the overload resolution than the template version, and is the one that gets called when you copy construct an A<T> from an A<T>. This can be illustrated with a simple example, with a user defined A(const A&):
#include <iostream>
template<class T>
struct A
{
A() {};
A(const A& a) {
std::cout << "copy constructor used for A" << std::endl;
}
template<class S>
A(const A<S>& a) {
std::cout << "constructor template used for A" << std::endl;
}
};
int main()
{
A<int> ai;
A<double> ad = ai; / calls template conversion contructor
A<int> ai2 = ai; // calls copy constructor A(const A&);
}
Per Paragraph 12.8/7 of the C++11 Standard:
If the class definition does not explicitly declare a copy constructor, one is declared implicitly.
Moreover, per Paragraph 12.8/2:
A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&,
volatile X& or const volatile X&, and either there are no other parameters or else all other parameters
have default arguments (8.3.6).
Therefore, the compiler will generate an implicit copy constructor here that gets invoked during the execution of this line:
A<int> aa = a;
This explains why you do not see a corresponding output.
This is because templated ctor is not a copy ctor. See also https://stackoverflow.com/a/1249848/341065.

Will a class recall its constructor when pushed in a std::vector?

if for example I do:
FOO foo;
foovect.push_back(foo);
where FOO is a class with a default constructor.
Will the constructor be called only when foo is put on the stack, or is it called again when pushed into the std::vector?
Thanks
I'm doing:
OGLSHAPE::OGLSHAPE(void)
{
glGenBuffersARB(GL_ARRAY_BUFFER_ARB,&ObjectVBOInt);
}
FOO foo; would call the constructor.
foovect.push_back(foo); would call the copy constructor.
#include <iostream>
#include <vector>
class FOO
{
public:
FOO()
{
std::cout << "Constructor" << std::endl;
}
FOO(const FOO& _f)
{
std::cout << "Copy Constructor" << std::endl;
}
};
int main()
{
FOO foo;
std::vector<FOO> foovect;
foovect.push_back(foo);
}
Output for this:
Constructor
Copy Constructor
No, the copy constructor is used, i.e. the one that looks like this:
FOO( const FOO & f );
A default copy constructor is provided by the compiler, if you don't provide one yourself.
When you do a push_back, your object is copied into the vector. That means the copy constructor for your object gets called. All the standard library containers deal with copies of objects, not the object themselves. If you want that behavior, you'll need to resort to using pointers.