0

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

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

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

请注意:在写一个简短的自包含问题时,我为分别具有圆的 x 和 y 坐标的向量写了 sinx 和 cosy。我可以改变这一点,但我会让下面的答案无效。角度的正弦给出 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;
}

非常感谢您的努力。

赫特珀凡

4

3 回答 3

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))
{ }

使用一个体面的编译器,返回值优化将意味着没有额外的开销(除了做两个for循环而不是一个循环)

编辑:在 C++11 中,您可以使用委托构造函数创建一个临时对象,该对象存储一些计算的结果,将其传递给另一个构造函数,然后使用结果初始化成员,有关更多详细信息,请参见重载 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)
{ }
于 2013-08-28T10:59:53.633 回答
2

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

当一个变量没有通过初始化列表初始化时,它首先被默认初始化(见这里),然后这个默认(通常未初始化)值在构造函数体中被覆盖。应该通过在初始化列表中仅初始化一次变量来避免这种情况。

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

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

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

   // fill both like you did
} 

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

于 2013-08-28T11:18:55.897 回答
2

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

编辑:添加了完整的、独立的示例。

#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++ 向量实现,并且至少给出forward_iterator(begin, end)构造函数确定开始和结束迭代器之间的差异,并在复制之前分配内存,就像reserve被调用一样。因为forward_iterator它仍然不是最优的,因为差异是通过连续增加begin迭代器的副本来确定的。然而,情况并非如此random_access_iterator。通过制作我们的迭代器random_access_iterator,我们得到一个分配和恒定的时间。

于 2013-08-28T11:03:33.913 回答