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

我提出了一个使用new放置的解决方案,但主要担心它是否可移植,是否调用任何 undefined 的行为以及安全地处置分配的对象。为避免不必要的复制,也欢迎进行任何改进。这是一个完整的测试程序,该程序被编写为尽可能最小:

#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变量的指针调用其他内容,最终wrapper.init在其他地方被调用,从而接收到许多具有复杂生命周期的参数。

WrapA::init创建的对象是否安全地传递到main并适当地用WrapA::~WrapA处理?调用A::bar()时一切正常吗?代码有问题吗?

最佳答案

您可以看一下管理诸如wrapA之类的资源的类,您基本上需要问两个问题:

  • 是否正确管理其资源:正确的构造,分配,销毁。
  • 它的任何公共(public)数据或功能是否可能使资源管理方案容易损坏?

  • 让我们从1开始。我看到一些潜在的问题:
  • 该类具有一个数据成员,该数据成员表示容纳A的空间,但不一定是实际A。这是好的
  • 但是,wrapA的构造函数不会构造A,但是析构函数确实会尝试破坏A。因此,如果您忘记在wrapA上调用init,则会得到 undefined 的行为。我会改变这个设计;最基本的方法是使用 bool(boolean) 标志来跟踪A是否已实际构建。
  • 但是,wrapA将获得自动构造的副本构造函数/赋值(不推荐使用)。这些自动生成的函数将不会正确调用A的复制构造函数/赋值,因为wrapA实际上并不拥有A,因此它们只会按位复制A。因此,如果A不平凡,则这些功能将无法正常工作。您应该显式地编写这两个函数,或者=删除它们,以便使wrapA不可复制。尽管wrapA既不可复制又不可移动,所以使用
  • 可能很烦人

    至于2:
  • getA函数很好,因为它返回一个副本,所以不提供内部资源的句柄

  • 简而言之,wrapA并不是完全错误的,因为您可以完美地使用它(如您所展示的)。但是,这也不完全正确。它不能满足您期望的c++类满足的要求,因此,我认为使用wrapA编写错误代码很容易。我认为,如果您解决有关析构函数以及复制构造函数/赋值的问题,使用它会更加安全。

    关于c++ - 全新放置,按值(value)返回并安全处置临时拷贝,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31091223/

    10-11 11:06