C++ 放置新副本、按值返回并安全地处置临时副本

C++ 放置新副本、按值返回并安全地处置临时副本,c++,c++11,placement-new,C++,C++11,Placement New,由于复杂的情况(在前面的问题中解释过),我想通过函数X的值返回一个对象,但在另一个函数Y中创建它,该函数Y是由X间接调用的。在它们之间,调用堆栈中有第三方代码,在传递对象时,这些代码是不合作的。X只能将指针传递给Y并接收回指针 我提出了一个使用placement new的解决方案,但主要担心它是否可移植,是否不调用任何未定义的行为,是否安全地处理分配的对象。我们也欢迎为避免不必要的拷贝而进行的任何改进。下面是一个完整的测试程序,它被编写成尽可能少的程序: #include <new>

由于复杂的情况(在前面的问题中解释过),我想通过函数X的值返回一个对象,但在另一个函数Y中创建它,该函数Y是由X间接调用的。在它们之间,调用堆栈中有第三方代码,在传递对象时,这些代码是不合作的。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);
}
输出显示A通过3个不同的内存地址,在整个时间内保持有效,并且所有副本似乎都被正确销毁。在本例中,
test
直接调用
init
,但在实际情况中,
test
使用指向
wrapper
变量的指针调用其他对象,最终,
wrapper.init
在其他地方被调用,接收到大量具有复杂生存期的参数


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

您可以查看一个管理资源的类,比如WRPA,您需要问两个问题:

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

    • 该类有一个数据成员,该成员表示容纳a的空间,但不一定是实际的a
    • 但是,wrapA的构造函数不构造A,但是析构函数确实尝试析构函数A。因此,如果您忘记对wrapA调用init,您将获得未定义的行为。我会改变这个设计;最基本的方法是使用一个布尔标志来跟踪a是否实际被构造
    • 但是,wrapA将自动构造副本构造函数/赋值(不推荐使用)。这些自动生成的函数不会正确调用A的复制构造函数/赋值,因为WRPA实际上并不拥有A,它们只会按位复制A。因此,如果A是非平凡的,这些函数将无法正常工作。您应该显式地编写这两个函数,或者=删除它们,这样您的WRPA就变得不可压缩了。尽管如此,wrapA将是不可压缩和不可移动的,因此与之合作可能很烦人
    至于第二点:

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

    简而言之,wrapA并不是完全错误的,因为您可以很好地使用它(正如您所演示的)。然而,这也不是完全正确的。它不符合你期望C++类满足的保证,因此我认为使用WrAPA编写BoGy代码将是很容易的。我认为,如果您解决了有关析构函数和复制构造函数/赋值的问题,使用它会更安全。

    感谢您的分析。WrapA只在test()中使用,而且两者都是大量模板化的,因此不太可能在其他地方使用。然而,我意识到在调用init之前可能会抛出一个异常,因此仍然需要该标志。我还将删除WrapA的自动生成赋值功能。最好是让WrapA的构造函数默认构造A()(如果您确定A将始终具有默认构造函数)。那么你以后就不需要那么多的特殊情况了。然后,您的init函数可以遵循CAS(复制和交换习惯用法),以确保异常安全。如果默认构造函数始终可用,我可以在test()中直接分配一个,并在其他地方直接分配给它,这样就不需要WRPA。然而,这是一个库,它应该与用户抛出的任何类一起工作。不幸的是,如果不要求A有一个复制构造函数,我似乎无法实现这一点。你可以实现它,这样如果A只有一个移动构造函数,它就可以工作。在这种情况下,getA方法将从WrapA“窃取”A,并留下一个不同但有效的A。如果a是一种具有非堆栈资源(如堆内存)的类型,这可能是一个提高效率的好主意。但是,除非您确定需要处理不可默认构造的类型,否则我可能会采取简单的方法。考虑到您的限制,使用默认的可构造类型将更容易、更高效。您的意思是将getA更改为return(std::move(*reinterpret_cast(&data))?这似乎有效,我不知道这可能是有效的!您确定更简单、默认可构造的情况会更有效吗?它仍然需要一个move赋值,在您最近的建议之后,这段代码(省略了debug())需要一个move构造函数调用。看起来这种更复杂的方法在最坏的情况下也同样有效。看起来对齐的_存储可以用匿名联合来代替,也消除了强制转换指针的需要。
    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