Boost: Store Pointers to Distributions in Vector

2019-04-12 02:42发布

问题:

Hi Stack Exchange Experts,

I'm trying to collect pointers to different statistical distributions provided by Boost in one vector. If distributions would be derived from one (virtual) parent class I could write something like

std::vector<Parent> v;

boost::math::normal_distribution<double> n;
boost::math::students_t_distribution<float> t(4);

boost::math::normal_distribution<double> *p1 = new boost::math::normal_distribution<double>(n);
boost::math::students_t_distribution<float> *p2 = new boost::math::students_t_distribution<float>(t);
v.push_back(p1);
v.push_back(p2);

and then iterate over the vector and apply functions etc. to the dereferenced pointers. But since this is not the case I don't really know how to store the pointers in one place?

So my question is, if there is a way to store pointers to different template classes in one variable/list/vector... (that can be handled conveniently like std::vector for example).

Remark that for example the Boost pdf density function can be applied to the dereferenced pointers regardless of the specific type (so storing them in one vector makes sense in some cases).

//////////////////////////////////////////////////////////////////////////

I played around with the different (nice) answers and finally decided to stick to boost::variant in conjunction with boost::static_visitor. Below is a full application that does what I outlined in my original question:

#include <boost/math/distributions.hpp>
#include <boost/variant.hpp>
#include <vector>
#include <iostream>

//template based visitor to invoke the cdf function on the distribution
class cdf_visitor_generic : public boost::static_visitor<double>
{
    public:

        //constructor to handle input arguments 
    cdf_visitor_generic(const double &x) : _x(x) {}

    template <typename T>
    double operator()(T &operand) const {
    return(boost::math::cdf(operand,_x));
    }

    private:
    double _x;
};

//shorten typing
typedef boost::variant< boost::math::normal_distribution<double>, boost::math::students_t_distribution<double> > Distribution;

int main (int, char*[])
{
//example distributions
boost::math::normal_distribution<double> s;
boost::math::students_t_distribution<double> t(1);

//build a variant
 Distribution v = t;

//example value for evaluation
double x = 1.96;

//evaluation at one point
double y = boost::apply_visitor( cdf_visitor_generic(x),v);
std::cout << y << std::endl;


//build a vector and apply to all elements of it:
std::vector<Distribution> vec_v;

vec_v.push_back(s);
vec_v.push_back(t);


for (std::vector<Distribution>::const_iterator iter = vec_v.begin(); iter != vec_v.end(); ++iter){

    //apply cdf to dereferenced iterator
    double test = boost::apply_visitor( cdf_visitor_generic(x), *iter);
    std::cout << test << std::endl;
}
return 0;
}

The only drawback I see is that the type of distribution needs to be explicitly specified (in the variant) so it could be that boost::any adds more freedom.

Thank you for the great help!

Hank

回答1:

You can use a variant:

std::vector<boost::variant<
    boost::math::normal_distribution<double>,
    boost::math::students_t_distribution<float>
> > v;

boost::math::normal_distribution<double> n;
boost::math::students_t_distribution<float> t(4);

v.push_back(n);
v.push_back(t);

I have several answers that show how to use these elements "polymorphically" (though the polymorphism is by statically compile typeswitching, instead of vtable dispatch). I'll add a link or two soon.

  • Generating an interface without virtual functions?
  • Avoid RTTI when dealing with pairs of objects
  • More involved: boost::mpl::fold for double parameter abstraction

Some of the linked answers show the "manual" approach to type erasure

PS. I should probably mention boost::any too, but I dislike it for several reasons. I shan't recommend it for this purpose.



回答2:

You can't store pointers to unrelated types in single vector. One way to achieve this would be to make a vector of void*:

std::vector<void*>

But I would strongly discourage you from doing this as this is not much of a c++ way.

Better solution would be to create custom class hierarchy for storing different kinds of pointers, for example:

class DistributionBase {
public:
    virtual ~DistributionBase() {}
}

template<typename T>
class Distribution : public DistributionBase {
public:
    typedef T DistributionType;
    T* distribution;

    Distribution(T* d) : distribution(d) {}
    ~Distribution() { delete distribution; }
}


template<typename T>
Distribution<T>* make_distribution(T* d) {
    return new Distribution<T>(d);
}

And then you can use it as follows:

std::vector<DistributionBase*> distributions;
distributions.push_back(make_distribution(new boost::math::normal_distribution<double>(n)))
distributions.push_back(make_distribution(new boost::math::students_t_distribution<float>(n)))

The problem is, that you have to store the type of distribution somewhere so you can static_cast to correct type:

boost::math::normal_distribution<double>* d = static_cast< Distribution<boost::math::normal_distribution<double> > >(distributions[0])->distribution;

This is just a snippet that should show you the point not a complete example.



回答3:

You can wrap pointers around your common base class. Here I will use Template Method Pattern:

class Distribution {
public:
    double pdf( double d) { doPdf( d)};
private:
    virtual double doPdf( double d) {} = 0;
    virtual ~Distribution() {}
};

class NormalDistribution : public Distribution {
private:
    boost::math::normal_distribution<double> nd;
    double doPdf( double d) { return pdf( nd, d);}
};

class StudentsTDistribution : public Distribution {
private:
    boost::math::students_t_distribution<double> std;
    double doPdf( double d) { return pdf( std, d);}
};

usage:

std::vector< boost::shared_ptr<Distribution> > v;
v.push_back( boost::make_shared<NormalDistribution>());
v.push_back( boost::make_shared<StudentsTDistribution>());
v[0]->pdf( 0.5); // draw from Gauss's distribution
v[1]->pdf( 0.5); // draw from fatter tails - t Student distribution