views:

246

answers:

4

I have:

class Foo;

class Bar {
  Foo foo;
  Bar(): foo(foo) {};
}

Bar bar;

At this point, is

bar.foo // <--- how is this initialized?

[This question arose from a buggy ref-counted pointer implemntation; I could have sworn that I ensured each pointer was pointing at something non-null; but I ended up with a pointer that pointed at something NULL.]

+12  A: 

foo is fully initialized once you've entered the body of the constructor (that's the guaranteed general case; specifically once it has finished initializing in the initialize list.)

In your case, you are copy-constructing from a non-constructed object. This results in undefined behavior, per §12.7/1 (thank you, gf):

For an object of non-POD class type (clause 9), before the constructor begins execution and after the destructor finishes execution, referring to any nonstatic member or base class of the object results in undefined behavior.

In fact, it gives this example:

struct W { int j; };
struct X : public virtual W { };
struct Y {
    int *p;
    X x;
    Y() : p(&x.j) // undefined, x is not yet constructed
    { }
};

Note, the compiler is not required to give a diagnosis of undefined behavior, per §1.4/1. While I think we all agree it would be nice, it simply isn't something the compiler implementers need to worry about.


Charles points out a loophole of sorts. If Bar has static storage and if Foo is a POD type, then it will be initialized when this code runs. Static-stored variables are zero-initialized before an other initialization runs.

This means whatever Foo is, as long as it doesn't need a constructor to be run to be initialized (i.e., be POD) it's members will be zero-initialized. Essentially, you'll be copying a zero-initialized object.

In general though, such code is to be avoided. :)

GMan
Yes, but there's a reference to `foo` _before_ the body of the constructor.
Chris Jester-Young
@Chris: I just saw that, I've added. I will look for the standard answer.
GMan
@Gman: I think you're right. However, if i'm copy-constructing from a non-constructed object, why does the compiler let me do this? This is clearly my fault, but I spent 4-5 hours of my life tracking this down. :-)
anon
I'm pretty sure objects are constructed after the initilisation list of the constructor completes, but before the constructor is called. This allows bypassing the default constructor for custom initialisation.
Cameron
@anon: Because its undefined behaviour, i *think* §12.7/1 should cover that - referring to a non-static member before the constructor executes results in undefined behaviour.
Georg Fritzsche
I'm not sure why that is allowed, but it's definitely undefined behavior.
Travis Gockel
@Cameron: The members of an object are initialized once their initialization in the list has finished. That is, once the body of the constructor is entered we are guaranteed that the members are fully initialized. The object itself is fully constructed once the constructor successfully returns.
GMan
@anon: The compiler is not required to give a diagnosis for undefined behavior. This is in section 1.4/1.
GMan
@GMan: OK, thanks for the definitive answer.
Cameron
We don't have the definition of `Foo` or its destructor so I don't believe that you can say, out of hand, that the initialization of `bar` has undefined behaviour. As `bar` has static storage duration it is zero-initialized before any other initialization occurs, so when the 'construction-from-self' occurs, `bar.foo` already has a known value. It is perfectly feasible that the constructor of `Foo` can run without causing undefined behaviour (it might do nothing!).
Charles Bailey
@Charles: I'm not sure I understand. It will be default-constructed, so we are for sure going to run across `foo` copying an uninitialized variable. Regardless of how Foo is defined, `foo` is definitely not initialized.
GMan
Actually, on reflection, the copy constructor would probably have to be compiler supplied as `Foo` would need to be a POD to avoid UB.
Charles Bailey
@Charles: Totally regardless of what Foo is, it's uninitialized when this constructor runs, which it will when the variable is instantiated during static construction.
GMan
8.5 [dcl.init] says that very object of static storage initialization is zero initialized before any other initialization occurs. i.e. before constructors run.
Charles Bailey
@Charles: Huh, I never knew they were still zero-initialized even with constructors. I think I'll add this to the answer; good call.
GMan
Re-reading 12.7, though, it may still be UB by this rule because it's Bar that must be POD not just `Foo` to avoid UB?? Anyway, it's not necessarily UB due to `Foo` having an unspecified value because this isn't the case.
Charles Bailey
@Charles: No, I think only `Foo` has to be POD. This sounds like a question for comp.std.c++.
GMan
In your quote there is the phrase "...before the constructor begins execution...". Does execution of the constructor mean the body of the constructor or is the initialiser list counted as well?
Troubadour
@Troubadour: I'm fairly sure it means constructor body, but I will look later.
GMan
A: 

Isn't Foo using a default intrinsic constructor, with the initialisation list invoking that default constructor automatically to initialise the object?

Chris Dennett
+3  A: 

A slightly expanded version of your code seems to indicate that no, foo is never initialized; you would seem to have undefined behavior. In this example, "Foo()" is never printed, indicating no instance of Foo is ever constructed:

#include <iostream>

class Foo {
public:
    Foo() { std::cerr << "Foo()"; }
};

class Bar {
public:
    Foo foo;
    Bar(): foo(foo) {};
};

int main() {
    Bar bar;
}
meagar
Nice/clever test.
anon
It's not so different from the classic `int main() {Foo foo = foo;}`. That, too, compiles. With `int` instead of `Foo`, both VC and Comeau give me a warning. With `Foo`, both fail to give one.
sbi
+5  A: 
Bar(): foo(foo) {};

This will call the copy constructor of foo, thus copy-constructing from a non-initialized object. That will result in undefined behavior, except if you have implemented a copy constructor that handles that specific case, for example:

class Foo
{
    public:
        Foo()
        {
            std::cout << "Foo()";
        }

        Foo(const Foo& from)
        {
            if(this == &from) std::cout << "special case";
            else std::cout << "other case"; 
        }
};

But that special case is normally used for other purposes, like cheap copies of strings (when using a string class). So don't try and exploit that special case ;)

AndiDog
Interesting. I'm curious if this is covered by the quote GMan makes about referring to any nonstatic member resulting in undefined behaviour i.e. is taking the address of the member actually guaranteed to work?
Troubadour
@Troubadour: The memory for the class (and its members) is already allocated, so member addresses are correct when calling the constructor.
AndiDog