Pro*_*ala 7 c++ destructor smart-pointers ownership
我经常遇到一种情况,当我有复杂的类(例如,实现一些数值算法,如偏微分方程求解器)和数据数组时,它可以根据用例拥有或从外部上下文绑定。问题是如何为这样的类制作健壮的析构函数。简单的方法是制作布尔标志,指示是否拥有该数组。例如
// simplest example I can think about
class Solver{
int nParticles;
bool own_position;
bool own_velocity;
double* position;
double* velocity;
// there is more buffers like this, not just position and velocity, but e.g. mass, force, pressure etc. each of which can be either owned or binded externally independently of each other, therefore if there is 6 buffers, there is 2^6 variants of owership (e.g. of construction/destruction)
void move(double dt){ for(int i=0; i<n; i++){ position[i]+=velocity[i]*dt; } }
~Solver(){
if(own_position) delete [] position;
if(own_velocity) delete [] velocity;
}
};
Run Code Online (Sandbox Code Playgroud)
自然,这促使围绕数组指针制作模板包装器(我应该称其为智能指针吗?):
template<typename T>
struct Data{
bool own;
T* data;
~Data{ if(own)delete [] T; }
};
class Solver{
int nParticles;
Data<double> position;
Data<double> velocity;
void move(double dt){ for(int i=0; i<n; i++){ position.data[i]+=velocity.data[i]*dt; } }
// default destructor is just fine (?)
};
Run Code Online (Sandbox Code Playgroud)
题:
编辑:要弄清楚是什么bind to external contex
意思(正如 Albjenow 建议的那样):
案例 1)私有/内部工作数组(无共享所有权)
// constructor to allocate own data
Data::Data(int n){
data = new double[n];
own = true;
}
Solver::Solver(int n_){
n=n_;
position(n); // type Data<double>
velocity(n);
}
void flowFieldFunction(int n, double* position, double* velocity ){
for(int i=0;i<n;i++){
velocity[i] = sin( position[i] );
}
}
int main(){
Solver solver(100000); // Solver allocates all arrays internally
// --- run simulation
// int niters=10;
for(int i=0;i<niters;i++){
flowFieldFunction(solver.n,solver.data.position,solver.data.velocity);
solver.move(dt);
}
}
Run Code Online (Sandbox Code Playgroud)
情况 2)绑定到外部数据数组(例如来自其他类)
Data::bind(double* data_){
data=data_;
own=false;
}
// example of "other class" which owns data; we have no control of it
class FlowField{
int n;
double* position;
void getVelocity(double* velocity){
for(int i=0;i<n;i++){
velocity[i] = sin( position[i] );
}
}
FlowField(int n_){n=n_;position=new double[n];}
~FlowField(){delete [] position;}
}
int main(){
FlowField field(100000);
Solver solver; // default constructor, no allocation
// allocate some
solver.n=field.n;
solver.velocity(solver.n);
// bind others
solver.position.bind( field.position );
// --- run simulation
// int niters=10;
for(int i=0;i<niters;i++){
field.getVelocity(solver.velocity);
solver.move(dt);
}
}
Run Code Online (Sandbox Code Playgroud)
这是一种简单的方法,可以完成您想要的操作,而无需自己编写任何智能指针(很难获得正确的细节)或编写自定义析构函数(这意味着来自其他特殊成员函数的更多代码和潜在错误)五规则要求):
#include <memory>
template<typename T>
class DataHolder
{
public:
DataHolder(T* externallyOwned)
: _ownedData(nullptr)
, _data(externallyOwned)
{
}
DataHolder(std::size_t allocSize)
: _ownedData(new T[allocSize])
, _data(_ownedData.get())
{
}
T* get() // could add a const overload
{
return _data;
}
private:
// Order of these two is important for the second constructor!
std::unique_ptr<T[]> _ownedData;
T* _data;
};
Run Code Online (Sandbox Code Playgroud)
该unique_ptr
成员保存自分配的数据,或者在使用外部拥有的数据时为空。原始指针在前一种情况下指向unique_ptr
内容,在后一种情况下指向外部内容。您可以修改构造函数(或者仅通过静态成员函数访问它们,例如DataHolder::fromExternal()
和DataHolder::allocateSelf()
返回DataHolder
使用适当构造函数创建的实例),以防止意外误用。
(请注意,成员按照在类中声明的顺序进行初始化,而不是按照成员初始值设定项列表的顺序进行初始化,因此在unique_ptr
原始指针之前添加 很重要!)
当然,此类不能被复制(由于unique_ptr
成员),但可以移动构造或分配(使用正确的语义)。开箱即用,应有尽有。