放置新的,按价值返回并安全地处置临时副本

时间:2015-06-27 16:59:05

标签: c++ c++11 placement-new

由于复杂的情况(在前面的问题Constructing an object to return by value elsewhere中解释),我希望通过函数X中的值返回一个对象,但是在由X间接调用的另一个函数Y中创建它。在它们之间有第三方代码调用堆栈,在传递对象时不会合作。 X只能将指针传递给Y并接收指针。

我已经提出了一个使用placement new的解决方案,但主要担心它是否可移植,不会调用任何未定义的行为并安全地处理已分配的对象。任何改进以避免不必要的副本也是受欢迎的。这是一个完整的测试程序,尽可能小写:

#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);
}

打印:

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

输出显示A通过3个不同的内存地址传递,在整个时间内保持有效,并且所有副本似乎都被正确销毁。在示例中,test直接调用init,但在现实生活中,test使用指向wrapper变量的指针调用其他内容,最终{{1}在其他地方调用,接收许多具有复杂生命周期的参数。

wrapper.init中创建的对象是否安全地传递到WrapA::init并在main中妥善处理?调用WrapA::~WrapA时一切正常吗?代码有问题吗?

1 个答案:

答案 0 :(得分:1)

您可以查看管理像wrapA这样的资源的类,您需要基本上提出两个问题:

  1. 是否正确管理其资源:正确构建,分配,销毁。
  2. 是否有任何公共数据或功能可能导致资源管理方案容易损坏?
  3. 让我们从1开始。我看到了一些潜在的问题:

    • 该类有一个数据成员,表示持有A的空间,但不一定是实际的A.这很好
    • 但是,wrapA的构造函数不构造A,但是析构函数确实试图破坏A.所以如果你忘记在wrapA上调用init,你将得到未定义的行为。我改变了这个设计;最基本的方法是使用布尔标志跟踪A是否实际构建过。
    • 但是,wrapA将获得自动构造的复制构造函数/赋值(不推荐使用)。这些自动生成的函数无法正确调用A的复制构造函数/赋值,因为wrapA实际上并不拥有A,它们只是按位复制A.因此,如果A是非平凡的,那么这些函数将无法正常工作。您应该显式地编写这两个函数,或者=删除它们,以便wrapA变为uncopyable。虽然,wrapA将是不可复制和不可移动的,所以使用
    • 可能很烦人

    至于2:

    • getA函数很好,因为它返回一个副本,因此不提供内部资源的句柄

    简而言之,wrapA并非完全错误,因为您可以完美地使用它(如您所示)。但是,它也不完全正确。它不能满足您期望c ++类满足的保证,因此我认为使用wrapA编写错误代码会很容易。我认为如果你修复了有关析构函数和复制构造函数/赋值的问题,那么使用起来会更安全。