C++ 标准::唯一性\u ptr<;T[]>;对于派生对象数组,使用deleted函数
在我的数值物理代码中,我需要使用C++ 标准::唯一性\u ptr<;T[]>;对于派生对象数组,使用deleted函数,c++,arrays,c++11,polymorphism,unique-ptr,C++,Arrays,C++11,Polymorphism,Unique Ptr,在我的数值物理代码中,我需要使用unique\u ptr创建一个派生对象数组,其类型为基类。通常,我会: // Header file of the Base class class Particle{ public: Particle(); // some constructor virtual ~Particle(); // virtual destructor because of polymorphism virtual functi
unique\u 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
};
优点是我能够以非常方便的方式使用数组,即
electronics[number].function()
,因为[]
中的增量值实际上是指向数组中对象Electron
的适当实例的内存地址。然而,使用原始指针会变得混乱,所以我决定使用智能指针
问题在于派生对象的定义。我可以做到以下几点:
std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);
std::unique_ptr<Particle[]> particles(new Particle[count]);
我一点也不喜欢get()
部分
我可以做到以下几点:
std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);
std::unique_ptr<Particle[]> particles(new Particle[count]);
除了我没有使用类Electron
的特定细节的部分之外,所有的事情都会很好,因此代码是无用的
有趣的是,让我们再做一件事,好吗
std::unique_ptr<Particle[]> electrons(new Electron[count]);
std::唯一电子(新电子[计数]);
轰
使用删除的函数“std::unique\u ptr::unique\u ptr(\u Up*)[带_Up=Electron;=void;\u Tp=Particle;\u Dp=std::default\u delete]”
发生了什么?您的设计的问题是对象是派生的和多态的,而不是对象数组 例如,
电子
可能具有粒子
不具有的附加数据。然后,电子
对象的大小将不再与粒子
对象的大小相同。因此,访问数组元素所需的指针算法将不再有效
对于指向数组的原始指针以及指向数组的唯一\u ptr
指针,都存在此问题。只有对象本身是多态的。如果您想使用它们而不冒风险,您需要一个指向多态对象的指针数组
如果您想寻找解释为什么应该避免这种设计的其他参数,您可以看看Scott Meyers的书“更有效的C++”中题为“第3项:从不多态地处理数组”的部分
备选方案:更改您的设计
例如,使用实类型的向量
创建对象。并使用指向多态粒子的向量
指针以多态方式使用这些对象:
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();
vectormyelectrons(计数);//我的实物商店
向量(count,nullptr);//我的多态访问适配器
转换(myelectrons.begin(),myelectrons.end(),ve.begin(),
[](粒子&e){return&e;});//使用算法轻松填充
for(auto x:ve)//简单地使用C++11来忘记容器类型和大小
x->function();
这里有一个:
std::unique\u ptr
防止射中你自己的脚,因为std::default\u delete
调用了delete[]
,这具有标准中规定的行为
如果删除表达式以一元::运算符开头,则
在全局范围中查找释放函数的名称。否则,,
如果使用delete表达式取消分配其
静态类型有一个虚拟析构函数,释放函数是
在动态类型的虚拟对象的定义点处选择的一个
析构函数(12.4)。117否则,如果使用删除表达式
解除分配类T或其数组的对象,静态和
对象的动态类型应相同,且解除分配
函数名在T的范围内查找
换句话说,代码如下所示:
Base* p = new Derived[50];
delete[] p;
#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;
}
这是一种未定义的行为
它似乎在某些实现中起作用-在那里,delete[]
调用查找分配数组的大小并调用元素上的析构函数-这要求元素具有已知的大小。由于派生对象的大小可能不同,指针算法出错,并且使用错误的地址调用析构函数
让我们回顾一下您的尝试:
std::unique_ptr<Particle[]> electrons(new Electron[count]);
如果是未定义的行为,您基本上会告诉编译器,delete[]
是释放推送到electrons
构造函数的资源的有效方法,如上所述,这是不正确的
对于加法或减法,如果表达式p或Q的类型为“指向cv T的指针”,其中T和数组元素类型不相似([conv.qual]),则行为未定义。[注意:特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针运算。-结束注意]
这意味着删除数组不仅是未定义的行为,而且索引也是未定义的行为
Base* p = new Derived[50]();
p[10].a_function(); // undefined behaviour
这对你意味着什么?这意味着您不应该以多态方式使用数组
多态性唯一安全的方法是使用std::unique_ptr
指向派生对象,如std::vector
(我们没有多态性地使用数组,但是数组中有多态性对象)
由于您提到性能至关重要,因此动态分配每个粒子将非常缓慢-在这种情况下,您可以:
- 使用对象池
- 利用飞锤模式
- 重构它以避免继承
- 直接使用
std::vector
或std::unique\u ptr
使用std::vector或std::array(如果您知道有多少个)std::unique\u ptr。大概是这样的:
Base* p = new Derived[50];
delete[] p;
#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个版本使用new(),而不是unique_ptr:
4.13924640 : std::vector
4.14430030 : std::array
4.14081580 : raw array
因此,您可以看到,在其他条件相同的情况下,发布版本中实际上没有任何差异。如果您希望保持与当前代码相近的状态,并单独跟踪计数,则可以使用std::unique\u ptr
注意
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())
4.13924640 : std::vector
4.14430030 : std::array
4.14081580 : raw array