按值放置新的 return 并安全地处理临时副本
Placement new, return by value and safely dispose temporary copies
由于复杂的情况(在前面的问题中解释过)我想return函数X中的一个对象,但是在X间接调用的另一个函数Y中创建它。它们之间调用堆栈中的第 3 方代码不会合作传递对象。 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
变量的指针调用其他内容,最终 wrapper.init
在别处被调用,接收许多具有复杂生命周期的参数。
在 WrapA::init
中创建的对象是否已安全地传递给 main
并在 WrapA::~WrapA
中得到适当处置? A::bar()
被调用时一切正常吗?代码有问题吗?
您可以查看一个 class 来管理像 wrapA 这样的资源,您基本上需要问两个问题:
- 它是否正确地管理其资源:正确的构建、分配、销毁。
- 它的任何 public 数据或函数是否可能允许轻易破坏资源管理方案?
让我们从 1 开始。我发现了一些潜在的问题:
- class 有一个数据成员代表 space 来保存 A,但不一定是实际的 A。这很好
- 但是,wrapA 的构造函数不会构造 A,但析构函数会尝试破坏 A。因此,如果您忘记在 wrapA 上调用 init,就会出现未定义的行为。我会改变这个设计;最基本的方法是使用一个布尔标志来跟踪 A 是否已实际构造。
- 然而,wrapA 将获得自动构造的副本 constructor/assignment(这已被弃用)。这些自动生成的函数不会正确调用 A 的副本 constructor/assignment,因为 wrapA 实际上并不拥有 A,它们只会按位复制 A。所以如果 A 是非平凡的,这些函数将无法正常工作。您应该明确地编写这两个函数,或者 = 删除它们,以便 wrapA 变得不可复制。虽然,wrapA 将既不可复制又不可移动,所以使用
可能很烦人
至于2:
- getA 函数很好,因为它 returns 一个副本,所以不提供内部资源的句柄
简而言之,wrapA 并非完全错误,因为您可以很好地使用它(如您所演示的)。然而,这也不完全正确。它不满足您期望 c++ class 满足的保证,因此我认为使用 wrapA 很容易编写错误代码。我认为如果你解决了关于析构函数和副本的问题,使用起来会安全得多 constructor/assignment.
由于复杂的情况(在前面的问题
我想出了一个使用 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
变量的指针调用其他内容,最终 wrapper.init
在别处被调用,接收许多具有复杂生命周期的参数。
在 WrapA::init
中创建的对象是否已安全地传递给 main
并在 WrapA::~WrapA
中得到适当处置? A::bar()
被调用时一切正常吗?代码有问题吗?
您可以查看一个 class 来管理像 wrapA 这样的资源,您基本上需要问两个问题:
- 它是否正确地管理其资源:正确的构建、分配、销毁。
- 它的任何 public 数据或函数是否可能允许轻易破坏资源管理方案?
让我们从 1 开始。我发现了一些潜在的问题:
- class 有一个数据成员代表 space 来保存 A,但不一定是实际的 A。这很好
- 但是,wrapA 的构造函数不会构造 A,但析构函数会尝试破坏 A。因此,如果您忘记在 wrapA 上调用 init,就会出现未定义的行为。我会改变这个设计;最基本的方法是使用一个布尔标志来跟踪 A 是否已实际构造。
- 然而,wrapA 将获得自动构造的副本 constructor/assignment(这已被弃用)。这些自动生成的函数不会正确调用 A 的副本 constructor/assignment,因为 wrapA 实际上并不拥有 A,它们只会按位复制 A。所以如果 A 是非平凡的,这些函数将无法正常工作。您应该明确地编写这两个函数,或者 = 删除它们,以便 wrapA 变得不可复制。虽然,wrapA 将既不可复制又不可移动,所以使用 可能很烦人
至于2:
- getA 函数很好,因为它 returns 一个副本,所以不提供内部资源的句柄
简而言之,wrapA 并非完全错误,因为您可以很好地使用它(如您所演示的)。然而,这也不完全正确。它不满足您期望 c++ class 满足的保证,因此我认为使用 wrapA 很容易编写错误代码。我认为如果你解决了关于析构函数和副本的问题,使用起来会安全得多 constructor/assignment.