Reputation: 4411
Since I know that it is generally much nicer to initialize all members in the initializer list of the constructor, I would like to know if it is also possible to do some more complicated construction in c++ inside the initializer list. In my program I would like to construct a class that initializes two of it's member vectors. Since I'm using them a Lot I would like to cache the contents, therefore I want to create the vectors at construction.
edit The main reason I want to cache these values that I can generate the the x and y coordinates from a circle with any radius without the need to recompute the sin and cosine values. So I'm using n (n_samples) for the granularity of sinx and cosy vector, which I use as lookup table internally.
So is it possible to do the initialization inside the initializer list, in such way that the constructor only needs to know how many samples it should create?
Please note: While writing a short self contained question I wrote sinx and cosy for the vectors with respectively x and y coordinates of a circle. I could change this but then I would invalidate answers below. The sinus of angle gives a y-coordinate and the cosine will give a x value typically.
#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;
}
many thanks for your effort.
Heteperfan
Upvotes: 0
Views: 220
Reputation: 3246
Another possible way to do it is to create iterators that lazily compute desired values, and use std::vector(begin, end)
-style constructor.
Edit: added complete, self-contained example.
#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;
};
For the record: by no means is that more efficient than your original version. To this end, reserve
mentioned by others I believe to be the only possible improvement. It's only an answer to your question, not a recommendation.
Edit 2: Actually, with a little bit of additional effort, this solution can be made as efficient as explicitly calling reserve. I've checked libstdc++ vector implementation, and given at least forward_iterator
, the (begin, end)
constructor determines the difference between begin and end iterators and allocates the memory before copying, just as if the reserve
was called. For just forward_iterator
it is still not optimal, as the difference is determined by successively incrementing the copy of the begin
iterator. This is not, hovewer, the case for random_access_iterator
. By making our iterator random_access_iterator
we get one allocation and constant time.
Upvotes: 2
Reputation: 171263
You can call a function to initialize member variables, so you can write a couple of helper functions:
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))
{ }
With a decent compiler the Return Value Optimization will mean there's no additional overhead (except for doing two for
loops instead of one)
Edit: In C++11 you can use delegating constructors to create a temporary object that stores the result of some calculations, pass that to another constructor, and then initialize members using the result, see page 24 of Overload 113 for more details (the article it refers to was in Overload 112.) This allows a single helper function to be used to provide the initialization for multiple members. Your example could become something like:
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)
{ }
Upvotes: 4
Reputation: 4950
You can initialize the vectors in the initializer list with Jonathan's solution. But there is no reason to do so.
When a variable is not initialized through the initializer list, it is first default initialized (see here) and this default (often uninitialized) value is then overwritten in the constructor body. This should be avoided by initializing a variable only once in the initializer-list.
Your example does not suffer from this problem. The vector's default constructor creates a perfectly valid empty vector both in your case and in Jonathan's solution. This vector is then filled with values, reallocating as it goes. If anything, Jonathan's solution is longer than yours and introduces unnecessary functions.
The only possible improvement is to preallocate the vectors and drop the unnecessary number_of_samples
member:
circular::circular( unsigned n ) {
sinx.reserve(n);
cosy.reserve(n);
// fill both like you did
}
If n is small, this is probably premature optimization, too.
Upvotes: 2