Placement new, return by value and safely dispose temporary copies

别来无恙 提交于 2019-12-14 04:02:45

问题


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?


回答1:


You can look at a class that manages resources like wrapA and you need to basically ask two questions:

  1. Does it correctly manage its resources: correct construction, assignment, destruction.
  2. Do any of its public data or functions potentially allow for easy corruption of the resource management scheme?

Let's start with 1. I see a few potential issues:

  • The class has a data member that represents the space to hold an A, but not necessarily an actual A. This is fine
  • However, the constructor for wrapA doesn't construct A, but the destructor does try to destruct A. So if you ever forget to call init on a wrapA, you will get undefined behavior. I'd change this design; the most basic way would be to have a boolean flag keeping track of whether an A had actually been constructed or not.
  • However, wrapA will get automatically constructed copy constructor/assignment (this is deprecated). These automatically generated functions will not correctly call A's copy constructor/assignment, since wrapA doesn't actually own an A, they will just copy A bitwise. So if A is non-trivial, these functions will not work properly. You should explicitly either write these two functions, or = delete them so you wrapA becomes uncopyable. Although, wrapA will then be both uncopyable and unmovable, so perhaps annoying to work with

As for 2:

  • the getA function is fine, as it returns a copy, so does not provide a handle to internal resources

In short, wrapA is not entirely wrong, as you can use it perfectly fine (as you demonstrated). However, it's not entirely right either. It doesn't meet the guarantees you'd expect a c++ class to meet, and therefore I think it will be easy to write buggy code using wrapA. I think it will be much safer to use if you fix the issues regarding the destructor, and the copy constructor/assignment.



来源:https://stackoverflow.com/questions/31091223/placement-new-return-by-value-and-safely-dispose-temporary-copies

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!