Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is passing a C++ object into its own constructor legal?

I am surprised to accidentally discover that the following works:

#include <iostream>             int main(int argc, char** argv) {   struct Foo {     Foo(Foo& bar) {       std::cout << &bar << std::endl;     }   };   Foo foo(foo); // I can't believe this works...   std::cout << &foo << std::endl; // but it does... } 

I am passing the address of the constructed object into its own constructor. This looks like a circular definition at the source level. Do the standards really allow you to pass an object into a function before the object is even constructed or is this undefined behavior?

I suppose it's not that odd given that all class member functions already have a pointer to the data for their class instance as an implicit parameter. And the layout of the data members is fixed at compile time.

Note, I'm NOT asking if this is useful or a good idea; I'm just tinkering around to learn more about classes.

like image 573
Andrew Wagner Avatar asked Sep 16 '15 12:09

Andrew Wagner


People also ask

What should not be done in constructor?

The most common mistake to do in a constructor as well as in a destructor, is to use polymorphism. Polymorphism often does not work in constructors !

When you passed object to a copy constructor then it?

If an object is passed as value to the Copy Constructor then its copy constructor would call itself, to copy the actual parameter to the formal parameter. Thus an endless chain of call to the copy constructor will be initiated.

Why copy constructor is called when we pass an object as an argument?

Because passing by value to a function means the function has its own copy of the object. To this end, the copy constructor is called.

What happens if constructor of class A is made private C++?

If some constructor is private, it means that no one but the class itself (and friends) should be able to create instances of it using that constructor. Therefore, you can provide static methods like getInstance() to create instances of the class or create the instances in some friend class/method.


2 Answers

This is not undefined behavior. Although foo is uninitialized, you are using it a way that is allowed by the standard. After space is allocated for an object but before it is fully initialized, you are allowed to use it limited ways. Both binding a reference to that variable and taking its address are allowed.

This is covered by defect report 363: Initialization of class from self which says:

And if so, what is the semantics of the self-initialization of UDT? For example

 #include <stdio.h>   struct A {         A()           { printf("A::A() %p\n",            this);     }         A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }         ~A()          { printf("A::~A() %p\n",           this);     }  };   int main()  {   A a=a;  } 

can be compiled and prints:

A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8 

and the resolution was:

3.8 [basic.life] paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it is fully initialized, and it's permitted to pass it as an argument to a reference parameter as long as the reference can bind directly. Except for the failure to cast the pointers to void * for the %p in the printfs, these examples are standard-conforming.

The full quote of section 3.8 [basic.life] from the draft C++14 standard is as follows:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

  • an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,

  • the glvalue is used to access a non-static data member or call a non-static member function of the object, or

  • the glvalue is bound to a reference to a virtual base class (8.5.3), or

  • the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.

We are not doing anything with foo that falls under undefined behavior as defined by the bullets above.

If we try this with Clang, we see an ominous warning (see it live):

warning: variable 'foo' is uninitialized when used within its own initialization [-Wuninitialized]

It is a valid warning since producing an indeterminate value from an uninitialized automatic variable is undefined behavior. However, in this case you are just binding a reference and taking the address of the variable within the constructor, which does not produce an indeterminate value and is valid. On the other hand, the following self-initialization example from the draft C++11 standard:

int x = x ; 

does invoke undefined behavior.

Active issue 453: References may only bind to “valid” objects also seems relevant but is still open. The initial proposed language is consistent with Defect Report 363.

like image 175
Shafik Yaghmour Avatar answered Nov 12 '22 07:11

Shafik Yaghmour


The constructor is called at a point where memory is allocated for the object-to-be. At that point, no object exists at that location (or possibly an object with a trivial destructor). Furthermore, the this pointer refers to that memory and the memory is properly aligned.

Since it's allocated and aligned memory, we may refer to it using lvalue expressions of Foo type (i.e. Foo&). What we may not yet do is have an lvalue-to-rvalue conversion. That's only allowed after the constructor body is entered.

In this case, the code just tries to print &bar inside the constructor body. It would even be legal to print bar.member here. Since the constructor body has been entered, a Foo object exists and its members may be read.

This leaves us with one small detail, and that's name lookup. In Foo foo(foo), the first foo introduces the name in scope and the second foo therefore refers back to the just-declared name. That's why int x = x is invalid, but int x = sizeof(x) is valid.

like image 42
MSalters Avatar answered Nov 12 '22 07:11

MSalters