samedi 27 juin 2015

Placement new, return by value and safely dispose temporary copies

Due to complicated circumstances (explained in earlier question Constructing an object to return by value elsewhere) I want to return an object by value from function X, but create it in another function Y indirectly called by X. Between them there is 3rd party code in the call stack that won't co-operate in passing the object around. X can only pass pointers to Y and receive a pointer back.

I've come up with a solution using placement new but mainly worry whether it's portable, doesn't invoke any undefined behavior and safely disposes of allocated objects. Any improvement to avoid unnecessary copies is also welcome. Here's an entire test program which was written to be as minimal as possible:

#include <new>
#include <type_traits>
#include <cstdio>

class A {
public:
    A() {
        printf("Create A @ %p\n", this);
    }

    A(const A &other) {
        printf("Copy A @ %p\n", this);
        printf("From another A %s @ %p\n", other.valid ? "OK" : "NOT OK", &other);
        valid = other.valid;
    }

    A(A &&other) {
        printf("Move A @ %p\n", this);
        printf("From another A %s @ %p\n", other.valid ? "OK" : "NOT OK", &other);
        valid = other.valid;
    }

    ~A() {
        printf("Destroy A %s @ %p\n", valid ? "OK" : "NOT OK", this);
        valid = false;
    }

    void bar() {printf("Hello, World! (A %s @ %p)\n", valid ? "OK" : "NOT OK", this);}

    bool valid = true;
};

class WrapA {
public:
    WrapA() {printf("Create wrapper! (A @ %p)\n", &data);}

    ~WrapA() {
        printf("Destroy wrapper! (A %s @ %p)\n", reinterpret_cast<A *>(&data)->valid ? "OK" : "NOT OK", &data);
        // Manually call destructor for instance created using placement new
        reinterpret_cast<A *>(&data)->~A();
    }

    void init() {
        ::new(&data) A();
    }

    A getA() {
        printf("Wrapper returning A %s @ %p\n", reinterpret_cast<A *>(&data)->valid ? "OK" : "NOT OK", &data);

        return(*reinterpret_cast<A *>(&data));
    }

    typename std::aligned_storage<sizeof(A), alignof(A)>::type data;
};

A debug(A data) {
    printf("Wrapper returned A %s @ %p\n", data.valid ? "OK" : "NOT OK", &data);
    return(data);
}

A test() {
    WrapA wrapper;

    wrapper.init();

    return(debug(wrapper.getA()));
}

int main(void) {
    test().bar();

    return(0);
}

It prints:

Create wrapper! (A @ 0x7fff1d6a5bde)
Create A @ 0x7fff1d6a5bde
Wrapper returning A OK @ 0x7fff1d6a5bde
Copy A @ 0x7fff1d6a5bdf
From another A OK @ 0x7fff1d6a5bde
Wrapper returned A OK @ 0x7fff1d6a5bdf
Move A @ 0x7fff1d6a5c0f
From another A OK @ 0x7fff1d6a5bdf
Destroy A OK @ 0x7fff1d6a5bdf
Destroy wrapper! (A OK @ 0x7fff1d6a5bde)
Destroy A OK @ 0x7fff1d6a5bde
Hello, World! (A OK @ 0x7fff1d6a5c0f)
Destroy A OK @ 0x7fff1d6a5c0f

The output shows that A gets passed through 3 different memory addresses, stays valid the entire time and all copies seem to get destroyed correctly. In the example, test calls init directly, but in the real-life case, test calls something else with the pointer to the wrapper variable, and eventually wrapper.init gets called elsewhere receiving a number of parameters with a complicated lifetime.

Is the object created in WrapA::init passed safely to main and appropriately disposed of in WrapA::~WrapA? Is everything OK when A::bar() gets called? Are there any problems with the code?

Aucun commentaire:

Enregistrer un commentaire