复杂的类成员构造

时间:2013-08-28 10:43:30

标签: c++ initialization

因为我知道在构造函数的初始化列表中初始化所有成员通常要好得多,我想知道是否也可以在初始化列表中的c ++中做一些更复杂的构造。在我的程序中,我想构建一个初始化它的两个成员向量的类。由于我正在使用它们很多我想缓存内容,因此我想在构造时创建向量。

编辑我想要缓存这些值的主要原因是我可以从任意半径的圆生成x和y坐标,而无需重新计算sin和余弦值。所以我使用n(n_samples)作为sinx和舒适向量的粒度,我在内部使用它作为查找表。

那么可以在初始化列表中进行初始化,这样构造函数只需要知道应该创建多少个样本吗?

请注意:在写一个简短的自包含问题时,我写了sinx并且对于分别具有圆的x和y坐标的向量是舒适的。我可以改变这一点,但后来我会使下面的答案无效。角度的正弦表示y坐标,余弦通常给出x值。

#include <vector>
#include <iostream>
#include <cmath>

class circular {

    public:

        circular( unsigned n = 20 );

        /* In my actual program I do much more 
         * complicated stuff. And I don't want
         * the user of this class to be bothered
         * with polar coordinates.
         */
        void print_coordinates( std::ostream& stream, double radius );

    private:

        unsigned            number_of_samples;

        std::vector<double> sinx;
        std::vector<double> cosy;
};

circular::circular( unsigned n )
    :
        number_of_samples(n) //I would like to initialize sinx cosy here.
{
    for ( unsigned i = 0; i < number_of_samples; ++i ){
        sinx.push_back( std::sin( 2*M_PI / number_of_samples*i ) );
        cosy.push_back( std::cos( 2*M_PI / number_of_samples*i ) );
    }
}

void
circular::print_coordinates( std::ostream& stream, double r)
{
    for ( unsigned i = 0; i < sinx.size(); ++i  ) {
        stream    << "{ "         << 
                     sinx[i] * r  << 
                     " , "        <<
                     cosy[i] * r  <<
                     " } "        <<
                     std::endl;
    }
}

int main () {
    circular c(20);
    c.print_coordinates(std::cout, 4);
    c.print_coordinates(std::cout, 5);
    return 0;
}
非常感谢你的努力。

Heteperfan

3 个答案:

答案 0 :(得分:4)

您可以调用函数来初始化成员变量,因此您可以编写几个辅助函数:

static std::vector<double> get_sinx(unsigned n)
{
    std::vector<double> sinx;
    sinx.reserve(n);
    for ( unsigned i = 0; i < n; ++i )
        sinx.push_back( std::sin( 2*M_PI / n*i ) );
    return sinx;
}

static std::vector<double> get_cosy(unsigned n)
{
    std::vector<double> cosy;
    cosy.reserve(n);
    for ( unsigned i = 0; i < n; ++i )
        cosy.push_back( std::cos( 2*M_PI / n*i ) );
    return cosy;
}

circular::circular( unsigned n )
    : number_of_samples(n), sinx(get_sinx(n)), cosy(get_cosy(n))
{ }

使用一个不错的编译器,Return Value Optimization意味着没有额外的开销(除了做两个for循环而不是一个)

编辑:在C ++ 11中,您可以使用委托构造函数创建一个临时对象,该对象存储一些计算的结果,将其传递给另一个构造函数,然后使用结果初始化成员,请参阅Overload 113的第24页了解更多详细信息(它引用的文章位于Overload 112。)这允许使用单个帮助函数为多个成员提供初始化。你的例子可能会变成:

circular::circular( unsigned n )
    : circular(n, makeCoords(n))
{ }

private:

struct Coords {
    std::vector<double> x;
    std::vector<double> y;
};

Coords makeCoords(unsigned n);  // calculate values

circular::circular( unsigned n, Coords coords )
    : number_of_samples(n), sinx(coords.x), cosy(coords.y)
{ }

答案 1 :(得分:2)

另一种可能的方法是创建延迟计算所需值的迭代器,并使用std::vector(begin, end) - 样式构造函数。

编辑:添加了完整的,自包含的示例。

#include <cmath>
#include <vector>
#include <iterator>

// Parameterized with the transforming function, so that we don't need separate
// implementations for sine and cosine
template <double f(double)>
struct fun_iterator : std::iterator<std::input_iterator_tag, double>
{
    fun_iterator(int n, int i = 0)
    : i(i), n(n)
    { }

    double operator * () const
    {
        return f(i * M_PI / n);
    }

    fun_iterator operator ++ (int) 
    {
        fun_iterator copy(*this);
        ++ (*this);
        return copy;
    }

    fun_iterator& operator ++ ()
    {
        ++ i;
        return *this;
    }

    friend bool operator == (const fun_iterator& a, const fun_iterator& b)
    {
        return a.i == b.i && a.n == b.n;
    }

    friend bool operator != (const fun_iterator& a, const fun_iterator& b)
    {
        return ! (a == b);
    }

private:
    int i;
    int n;
};

struct with_values
{
    with_values(int n)
    : sine(fun_iterator<std::sin>(n), fun_iterator<std::sin>(n, n))
    , cosine(fun_iterator<std::cos>(n), fun_iterator<std::cos>(n, n))
    { }

    std::vector<double> sine;
    std::vector<double> cosine;
};

对于记录:绝不比原始版本更有效。为此,我认为其他人提到的reserve是唯一可能的改进。这只是你问题的答案,而不是推荐。

编辑2:实际上,通过一些额外的努力,这个解决方案可以像显式调用reserve一样高效。我检查了libstdc ++ vector实现,至少给出了forward_iterator(begin, end)构造函数确定了开始和结束迭代器之间的区别,并在复制之前分配内存,就好像reserve是调用。仅对于forward_iterator,它仍然不是最优的,因为差异是通过连续递增begin迭代器的副本来确定的。这不是random_access_iterator的情况。通过使我们的迭代器random_access_iterator,我们得到一个分配和恒定时间。

答案 2 :(得分:2)

您可以使用Jonathan的解决方案初始化初始化列表中的向量。 但没有理由这样做。

如果未通过初始化列表初始化变量,则首先默认初始化(see here),然后在构造函数体中覆盖此默认值(通常为未初始化的值)。这应该通过在初始化列表中仅初始化一次变量来避免。

您的示例不会遇到此问题。向量的默认构造函数在您的情况下和Jonathan的解决方案中创建了一个完全有效的空向量。然后用向量填充该向量,随着它重新分配。如果有的话,Jonathan的解决方案比你的更长,并引入了不必要的功能。

唯一可能的改进是预先分配向量并删除不必要的number_of_samples成员:

circular::circular( unsigned n ) { 
   sinx.reserve(n);
   cosy.reserve(n);

   // fill both like you did
} 

如果n很小,这可能也是过早的优化。