7

当我有复杂的类(例如,实现一些数值算法,如偏微分方程求解器)时,我经常遇到这样的情况,它可以根据用例拥有或从外部上下文绑定数据数组。问题是如何为此类创建健壮的析构函数。简单的方法是制作布尔标志,指示数组是否拥有。例如

// 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);
   }
}
4

2 回答 2

2

这是一种简单的方法来做你想做的事,而不必自己编写任何智能指针(很难得到正确的细节)或编写自定义析构函数(这意味着来自其他特殊成员函数的更多代码和错误潜力由五规则要求):

#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成员),但可以被移动构造或分配(具有正确的语义)。开箱即用。

于 2019-12-03T12:56:47.977 回答
1

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

struct Solver {
    int nParticles;
    double* position;
    double* velocity;
};

struct Data {
    std::vector<double> position, velocity; // Alternatively, std::unique_ptr<double[]>.

    template<class T>
    static T* get(int size, std::vector<T>& own_data, T* external_data) {
        if(external_data)
            return external_data;
        own_data.resize(size);
        return own_data.data();
    }

    double* get_position(int nParticles, double* external_position) { return get(nParticles, position, external_position); }
    double* get_velocity(int nParticles, double* external_velocity) { return get(nParticles, velocity, external_velocity); }
};

struct SolverAndData {
    Data data;
    Solver solver;

    SolverAndData(int nParticles, double* external_position, double* external_velocity)
        : solver{
              nParticles,
              data.get_position(nParticles, external_position),
              data.get_velocity(nParticles, external_velocity)
          }
    {}

    SolverAndData(SolverAndData const&) = delete;
    SolverAndData& operator=(SolverAndData const&) = delete;
};

int main() {
    SolverAndData a(1, nullptr, nullptr);

    double position = 0;
    SolverAndData b(1, &position, nullptr);

    double velocity = 0;
    SolverAndData c(1, nullptr, &velocity);

    SolverAndData d(1, &position, &velocity);
}
于 2019-12-03T13:01:37.923 回答