Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Construct object with itself as reference?

I just realised that this program compiles and runs (gcc version 4.4.5 / Ubuntu):

#include <iostream>
using namespace std;

class Test
{
public:
  // copyconstructor
  Test(const Test& other);
};

Test::Test(const Test& other)
{
  if (this == &other)
    cout << "copying myself" << endl;
  else
    cout << "copying something else" << endl;
}

int main(int argv, char** argc)
{
  Test a(a);              // compiles, runs and prints "copying myself"
  Test *b = new Test(*b); // compiles, runs and prints "copying something else"
}

I wonder why on earth this even compiles. I assume that (just as in Java) arguments are evaluated before the method / constructor is called, so I suspect that this case must be covered by some "special case" in the language specification?

Questions:

  1. Could someone explain this (preferably by referring to the specification)?
  2. What is the rationale for allowing this?
  3. Is it standard C++ or is it gcc-specific?

EDIT 1: I just realised that I can even write int i = i;

EDIT 2: Even with -Wall and -pedantic the compiler doesn't complain about Test a(a);.

EDIT 3: If I add a method

Test method(Test& t)
{
  cout << "in some" << endl;
  return t;
}

I can even do Test a(method(a)); without any warnings.

like image 626
aioobe Avatar asked Dec 06 '10 16:12

aioobe


2 Answers

The reason this "is allowed" is because the rules say an identifiers scope starts immediately after the identifier. In the case

int i = i;

the RHS i is "after" the LHS i so i is in scope. This is not always bad:

void *p = (void*)&p; // p contains its own address

because a variable can be addressed without its value being used. In the case of the OP's copy constructor no error can be given easily, since binding a reference to a variable does not require the variable to be initialised: it is equivalent to taking the address of a variable. A legitimate constructor could be:

struct List { List *next; List(List &n) { next = &n; } };

where you see the argument is merely addressed, its value isn't used. In this case a self-reference could actually make sense: the tail of a list is given by a self-reference. Indeed, if you change the type of "next" to a reference, there's little choice since you can't easily use NULL as you might for a pointer.

As usual, the question is backwards. The question is not why an initialisation of a variable can refer to itself, the question is why it can't refer forward. [In Felix, this is possible]. In particular, for types as opposed to variables, the lack of ability to forward reference is extremely broken, since it prevents recursive types being defined other than by using incomplete types, which is enough in C, but not in C++ due to the existence of templates.

like image 158
Yttrill Avatar answered Sep 18 '22 23:09

Yttrill


I have no idea how this relates to the specification, but this is how I see it:

When you do Test a(a); it allocates space for a on the stack. Therefore the location of a in memory is known to the compiler at the start of main. When the constructor is called (the memory is of course allocated before that), the correct this pointer is passed to it because it's known.

When you do Test *b = new Test(*b);, you need to think of it as two steps. First the object is allocated and constructed, and then the pointer to it is assigned to b. The reason you get the message you get is that you're essentially passing in an uninitialized pointer to the constructor, and the comparing it with the actual this pointer of the object (which will eventually get assigned to b, but not before the constructor exits).

like image 31
Matti Virkkunen Avatar answered Sep 18 '22 23:09

Matti Virkkunen