可在运行时更改所有权的智能指针(C ++)

时间:2019-12-03 12:30:51

标签: 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;  
    }
};

自然,这会激发围绕数组指针的模板包装(我应该把它称为智能指针吗?):

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 (?)
};

问题:

  • 这必须是常见的模式,我要在这里重新发明轮子吗?
  • C ++标准库中是否有类似的东西? (抱歉,我是物理学家,而不是程序员)
  • 有什么要考虑的问题吗?

--------------------------------------------

编辑:要弄清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);
   }
}

情况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);
   }
}

2 个答案:

答案 0 :(得分:2)

一种解决方案是将数据所有权与您的求解器算法分开。让算法选择性地管理其输入的生命周期并不是一个好的设计,因为它会导致各个问题的纠缠。求解器算法应始终引用已经存在的数据。并且如果需要的话,还有另一个拥有数据的额外类,并且其生存期不短于算法的生存期,例如:

+----+---------+-----------+------------+---------------+---------------+-----------------+-------+-----------------+------------+-----------------+-------------+
| id | Stud_id | Stud_Name | Stud_Class | Parent_email1 | Parent_email2 | Subject_id (01) | marks | Subject_id (02) | marks (02) | Subject_id (03) | marks (03)  |
+----+---------+-----------+------------+---------------+---------------+-----------------+-------+-----------------+------------+-----------------+-------------+
|  1 |       1 | abc       |          6 | zyx@gmail.com | abc@yahoo.com |              10 |    45 |              11 |         43 |              12 |          50 |
+----+---------+-----------+------------+---------------+---------------+-----------------+-------+-----------------+------------+-----------------+-------------+

答案 1 :(得分:1)

这是一种简单的方法,无需您自己编写任何智能指针(很难正确地获得详细信息)或编写自定义析构函数(这将意味着从其他代码中获取更多的代码和潜在的错误),即可完成所需的操作rule of five要求的特殊成员函数:

#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;
};

https://godbolt.org/z/T4cgyy

unique_ptr成员保存自我分配的数据,或者在使用外部拥有的数据时为空。在前一种情况下,原始指针指向unique_ptr内容,在后一种情况下,指向原始内容。您可以修改构造函数(或仅使它们可以通过静态成员函数(如DataHolder::fromExternal()DataHolder::allocateSelf()进行访问),这些静态成员函数返回使用适当的构造函数创建的DataHolder实例)。

>

(请注意,成员是按照在类中声明的顺序进行初始化的,而不是按照成员初始值设定项列表的顺序进行初始化,因此在原始指针之前放置unique_ptr很重要!)

当然,该类不能被复制(由于unique_ptr成员),而可以被移动构造或分配(具有正确的语义)。即开即用。

相关问题