std::unique_ptr<T[]> 使用派生对象数组,使用 delete 函数
std::unique_ptr<T[]> with an array of derived objects, use of deleted function
在我的数值物理代码中,我需要使用unique_ptr
创建一个派生对象数组,它们的类型是基类。 通常,我会有:
// Header file of the Base class
class Particle{
public:
Particle(); // some constructor
virtual ~Particle(); // virtual destructor because of polymorphism
virtual function(); // some random function for demonstration
};
// Header file of the Derived class
class Electron : public Particle{
public:
Electron();
// additional things, dynamic_cast<>s, whatever
};
在我的代码后面,要使用 Base 类型指针创建派生对象数组,我会这样做
Particle* electrons = new Electron[count];
优点是我能够以非常方便的方式使用数组electrons[number].function()
,因为 []
中的增量值实际上是指向数组中对象Electron
的正确实例的内存地址。但是,使用原始指针会变得混乱,所以我决定使用智能指针。
问题在于派生对象的定义。我可以执行以下操作:
std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);
它创建多态电子阵列,甚至使用正确的delete[]
调用。问题在于调用数组的特定对象的方式,因为我必须这样做:
electrons.get()[number].function();
而且我不喜欢get()
部分,一点也不喜欢。
我可以执行以下操作:
std::unique_ptr<Particle[]> particles(new Particle[count]);
是的,Particle
使用
particles[number].function();
一切都会很好,除了我没有使用类Electron
的特定细节的部分,因此代码是无用的。
现在对于有趣的部分,让我们再做一件事,好吗?
std::unique_ptr<Particle[]> electrons(new Electron[count]);
繁荣!
use of deleted function ‘std::unique_ptr<_Tp [], _Dp>::unique_ptr(_Up*) [with _Up = Electron; <template-
parameter-2-2> = void; _Tp = Particle; _Dp = std::default_delete<Particle []>]’
这是怎么回事?
std::unique_ptr
正在防止向自己的脚开枪,正如std::default_delete<T[]>
所说的那样 delete[]
,它具有标准中指定的行为
如果删除表达式以一元 :: 运算符开头,则 在全局范围内查找释放函数的名称。否则 如果删除表达式用于释放其 静态类型有一个虚拟析构函数,释放函数是 在动态类型的虚拟定义点选择的一个 析构函数 (12.4(。117 否则,如果删除表达式用于 释放 T 类的对象或其数组,静态和 对象的动态类型应相同,并且释放 函数的名称在 T 的范围内查找。
换句话说,代码如下:
Base* p = new Derived[50];
delete[] p;
是未定义的行为。
它似乎适用于某些实现 - 在那里,delete[]
调用查找分配数组的大小并调用元素上的析构函数 - 这要求元素具有众所周知的大小。由于派生对象的大小可能不同,因此指针算术出错,并且使用错误的地址调用析构函数。
让我们回顾一下您尝试过的内容:
std::unique_ptr<Particle[]> electrons(new Electron[count]);
std::unique_ptr
的构造函数中有一个代码可以检测这些冲突,请参阅 CPP 首选项。
std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);
是未定义的行为,你本质上告诉编译器delete[]
是释放你推送给electrons
构造函数的资源的有效方法,如上所述,这不是真的。
。但是等等,还有更多(@T.C.的无价评论(:
对于加法或减法,如果表达式 P 或 Q 的类型为"指向 cv T 的指针",其中 T 和数组元素类型不相似 ([conv.qual](,则行为未定义。[ 注意:特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针算术。 — 尾注 ]
这意味着不仅删除数组是未定义的行为,而且索引也是如此!
Base* p = new Derived[50]();
p[10].a_function(); // undefined behaviour
这对你意味着什么?这意味着您不应该以多态方式使用数组。
多态性的唯一安全方法是使用指向派生对象的std::unique_ptr
,例如std::vector<std::unique_ptr<Particle>>
(我们在那里没有数组的多态使用,但那里有具有多态对象的数组(
由于您提到性能至关重要,因此动态分配每个Particle
会很慢 - 在这种情况下,您可以:
- 使用对象池
- 利用蝇量级图案
- 重构它以避免继承
- 直接使用
std::vector<Electron>
或std::unique_ptr<Electron[]>
。
设计的问题在于对象是派生的和多态的,而不是对象的数组。
例如,Electron
可能具有Particle
没有的其他数据。然后,Electron
对象的大小将不再与Particle
对象的大小相同。 因此,访问数组元素所需的指针算法将不再起作用。
原始指针以及到数组的unique_ptr
都存在此问题。 只有对象本身是多态的。 如果你想在没有切片风险的情况下使用它们,你需要一个指向多态对象的指针数组。
如果你寻找其他论据来解释为什么应该避免这种设计,你可以看看Scott Meyers的书"更有效的C++"部分,标题为"项目3:永远不要多态地处理数组"。
替代方案:更改设计
例如,使用实际类型的vector
来创建对象。 并使用指向多态Particle
指针的向量以多态方式使用这些对象:
vector<Electron>myelectrons(count); // my real object store
vector<Particle*>ve(count, nullptr); // my adaptor for polymorphic access
transform(myelectrons.begin(), myelectrons.end(), ve.begin(),
[](Particle&e){return &e;} ); // use algorithm to populate easlily
for (auto x: ve) // make plain use of C++11 to forget about container type and size
x->function();
这是一个现场演示:
array(如果你知道有多少个(的 std::unique_ptr。 像这样:
#include <vector>
#include <memory>
class A
{
public:
A() = default;
virtual ~A() = default;
};
class B : public A
{
public:
B() = default;
virtual ~B() = default;
};
int main(void)
{
auto v = std::vector<std::unique_ptr<A>>();
v.push_back(std::make_unique<A>());
v.push_back(std::make_unique<B>());
return 0;
}
编辑:在速度方面,我用3种方法进行了快速测试,这就是我发现的:
Debug
6.59999430 : std::vector (with reserve, unique_ptr)
5.68793220 : std::array (unique_ptr)
4.85969770 : raw array (new())
Release
4.81274890 : std::vector (with reserve, unique_ptr)
4.42210580 : std::array (unique_ptr)
4.12522340 : raw array (new())
最后,我做了一个测试,我对所有 3 个版本都使用 new(( 而不是 unique_ptr:
4.13924640 : std::vector
4.14430030 : std::array
4.14081580 : raw array
因此,您会看到在其他条件相同的情况下,发布版本实际上没有区别。
令人惊讶的是,还没有人建议只使用多态删除器。unique_ptr的默认删除器就是默认值。您可以更改它以执行任何您想要的操作,包括向上或向下投射。
它涉及一些转换,但如果需要,您可以将其隐藏在合适的界面后面。
http://coliru.stacked-crooked.com/a/35bd4c3674d7df07
不过,我不建议使用此进行指针索引。那仍然会完全被打破。
如果您想接近当前代码并单独跟踪计数,则可以使用 std::unique_ptr<std::unique_ptr<Particle>[]>
。
请注意,这不会让你绕过额外的间接寻址,如果可以的话,使用std::vector<std::unique_ptr<Particle>>
,从而包括长度和明智地使用reserve
不应该更慢。
- 销毁C++中动态分配的内存(数组对象)
- 数组对象的生存期是否在重用其元素存储时结束?
- 为什么顶点数组对象会导致错误?
- 具有纯虚函数和指针数组对象类型的父类的指针数组
- 这是使用构造函数初始化数组对象的最佳方法吗?
- OpenGL 顶点数组对象与 tinyobjloader
- 将数组/对象/结构列表从C#库中传递给C MFC应用程序
- C++ RapidJson 帮助反序列化数组对象
- ptrdiff_t可以表示指向同一数组对象元素的指针的所有减法吗?
- 检查成员函数是否返回临时对象或数组对象
- 为什么 std::variant 不能容纳数组对象类型,而联合可以?
- 当数组对象以函数参数传递时,为什么复制构造函数会自称
- 如何使用箭头指针打印出一类数组对象,这些对象中有多个分数
- C++17 std::shared_ptr<> 类数组对象的重载运算符 []
- 添加两个具有运算符重载的数组对象,从而导致分段错误
- opengl:两个不同的矢量可以绑定到同一个顶点数组对象吗
- 使用相同的数据填充数组对象或使用指针
- 方法用于最快的分配,并且不需要将动态大小的数组对象作为局部变量
- 如何将2d数组对象传递给c++中的函数
- ReferenceTable溢出(jni-android),数组对象释放