Search code examples
c++undefined-behaviorplacement-new

Using initialized variable after placement new from the constructor tripping UB?


Disregarding whether the following can be achieved through other, more safe constructs - I'm simply interested in whether or not the following results in a well-defined output.

Assume you have a struct A:

struct A {
    Foo* foo;    
}

And a struct B inheriting from it:

struct B : A {
    B() {
        foo->some_function(); // UB
    }
}

Sure enough if you were creating a B instance the normal way you'd trip UB, however...

template<typename R> 
R make_A() { // This acts like a constructor for As
    static_assert(std::is_base_of<A, R>::value, "R must derive from A");
    char r[sizeof(R)];
    ((R*)r)->foo = returns_some_valid_foo();
    new (r) R;

    return *((R*)r);
}

B b1; // Blows up (Could you somehow prevent this from compiling without changing B?)
B b2 = make_A<B>(); // Works fine?

Sheepishly assuming that C++ works like C somewhere under the hood, I'm guessing that this would be similar to having a struct instance in C, initializing it by hand, and then calling some method (in this case B's constructor) on the finished product.

Again, I'm not interested in whether you should do this or not, it's just a technical question.

EDIT:

If you wonder what this could be useful for, I could use it to pull out values into a plain struct from, say, a configuration file in a really terse manner. Yes it does use macros but call it a stub until C++ gets compile time reflection:

#define config_key($x, $def) $x = foo->get<decltype($x)>(#$x, ($def))   

struct Record : A {
    int    config_key(a, 3); // Second parameter is default value
    string config_key(b, "something");
}

auto record = make_A<Record>();

(Using A and foo here to stay consistent with what I wrote above, make_A is actually part of a class that does config)


Solution

  • This:

    ((R*)r)->foo = returns_some_valid_foo();
    

    is undefined behavior. There is no object of type R at r. Full stop. If you flip the two lines so that you create the R first, then you're fine (modulo r being insufficiently aligned).

    Or really, just:

    R r;
    r.foo = returns_some_valid_foo();
    return r;