放置新的,按价值返还,并安全处置临时副本
Placement new, return by value and safely dispose temporary copies
由于复杂的情况(在前面的问题中解释过在其他地方构造一个按值返回的对象),我想从函数X按值返回一个对象,但在X间接调用的另一个函数Y中创建它。X只能将指针传递给Y并接收回一个指针。
我已经提出了一个使用placementnew的解决方案,但主要担心它是否是可移植的,不会调用任何未定义的行为,并安全地处理分配的对象。为避免不必要的复制而进行的任何改进也是受欢迎的。这是一个完整的测试程序,它被写得尽可能地最小化:
#include <new>
#include <type_traits>
#include <cstdio>
class A {
public:
A() {
printf("Create A @ %pn", this);
}
A(const A &other) {
printf("Copy A @ %pn", this);
printf("From another A %s @ %pn", other.valid ? "OK" : "NOT OK", &other);
valid = other.valid;
}
A(A &&other) {
printf("Move A @ %pn", this);
printf("From another A %s @ %pn", other.valid ? "OK" : "NOT OK", &other);
valid = other.valid;
}
~A() {
printf("Destroy A %s @ %pn", 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 @ %pn", 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 @ %pn", 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等资源的类,基本上需要问两个问题:
- 它是否正确地管理其资源:正确的建造、分配和销毁
- 它的任何公共数据或功能是否可能导致资源管理计划容易被破坏
让我们从1开始。我看到了一些潜在的问题:
- 该类有一个数据成员,表示容纳a的空间,但不一定是实际的a。这很好
- 然而,wrapA的构造函数不构造A,但析构函数确实试图析构函数A。因此,如果您忘记在wrapA上调用init,您将得到未定义的行为。我会改变这个设计;最基本的方法是用一个布尔标志来跟踪a是否真的构造好了
- 然而,wrapA将自动构造复制构造函数/赋值(这是不推荐的)。这些自动生成的函数不会正确地调用A的复制构造函数/赋值,因为wrapA实际上并不拥有A,它们只会逐位复制A。因此,如果A不是平凡的,那么这些函数将无法正常工作。您应该显式地编写这两个函数,或者=删除它们,这样wrapA就变得不可复制了。尽管如此,wrapA将是不可复制和不可移动的,因此使用它可能会很烦人
对于2:
- getA函数很好,因为它返回一个副本,所以不提供内部资源的句柄
简而言之,wrapA并不是完全错误的,因为你可以很好地使用它(正如你所展示的)。然而,这也不完全正确。它不能满足您期望的c++类所能满足的保证,因此我认为使用wrapA编写有缺陷的代码会很容易。我认为,如果你解决了析构函数和复制构造函数/赋值的问题,使用它会更安全。
相关文章:
- 从不同线程使用int64的不同字节安全吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 虚拟决赛作为安全
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 通过网络、跨平台传递std::变体是否安全
- 用callgrind追踪不必要的副本
- 在std::thread中,joinable()然后join()线程安全吗
- 使用std::istream::peek()总是安全的吗
- 从值小于256的uint16到uint8的Endian安全转换
- 关于:C++中异常对象的范围:为什么我没有得到副本?
- 在为LINUX创建共享库时,如何避免STL的私有/弱副本
- 在c++队列中使用pop和visit实现线程安全
- 此副本分配操作安全吗?
- 免疫表中副本的设置是否安全,避免线程损坏
- 保留 std::initializer_list 的副本是否安全?理由是什么
- 复制/修改STL容器的副本是线程安全的
- 放置新的,按价值返还,并安全处置临时副本
- std::在没有提供副本的情况下,使用不安全的移动调整矢量大小