Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I initialize a std::vector with a size parameter and have each object constructed independently?

Tags:

c++

I like creating vectors with a given size and value, for example like this:

std::vector<std::string> names(10);

However, this a few times this led to unexpected results. For example in the following code each UniqueNumber turns out to have the same value:

#include <iostream>
#include <string>
#include <vector>


struct UniqueNumber
{
    UniqueNumber() : mValue(sInstanceCount++)
    {
    }

    inline unsigned int value() const
    {
        return mValue;
    }

private:
    static unsigned int sInstanceCount;
    unsigned int mValue;
};


int UniqueNumber::sInstanceCount(0);


int main()
{
    std::vector<UniqueNumber> numbers(10);
    for (size_t i = 0; i < numbers.size(); ++i)
    {
        std::cout << numbers[i].value() << " ";
    }
}

Console output:

0 0 0 0 0 0 0 0 0 0

It does make sense when looking at std::vector's constructor:

explicit vector(size_type __n,
                const value_type& __value = value_type(),
                const allocator_type& __a = allocator_type());

Apparently the vector was initialized with copies of the same object.

Is there also a way to have each object default constructed?

like image 283
StackedCrooked Avatar asked Jan 21 '11 18:01

StackedCrooked


4 Answers

generate or generate_n are purpose-designed for just what you want to do. Here's a complete example:

#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>
using namespace std;

class Foo
{
public:
    Foo() : n_(++water_) {};
    operator unsigned () const { return n_; }
private:
    static unsigned water_;
    unsigned n_;
};

Foo make_foo()
{
    return Foo();
}

unsigned Foo::water_ = 0;

int main()
{
    vector<Foo> v_foo;
    generate_n(back_inserter(v_foo), 10, &make_foo);
    copy(v_foo.begin(), v_foo.end(), ostream_iterator<unsigned>(cout, " "));
}

Output: 1 2 3 4 5 6 7 8 9 10

like image 124
John Dibling Avatar answered Oct 12 '22 03:10

John Dibling


Vector is copy-constructing the elements for initialization.

Try that:

#include <iostream>
#include <string>
#include <vector>


struct UniqueNumber
{
    UniqueNumber(bool automatic = true) 
        : mValue(automatic?sInstanceCount++:Special)
    { }

    UniqueNumber(UniqueNumber const& other) 
        : mValue(other.mValue==Special?sInstanceCount++:other.mValue)
    { }

    unsigned int value() const
    {
        return mValue;
    }

private:
    static int sInstanceCount;
    unsigned int mValue;
    static unsigned int const Special = ~0U;
};


int UniqueNumber::sInstanceCount(0);


int main()
{
    std::vector<UniqueNumber> numbers(10,UniqueNumber(false));
    for (size_t i = 0; i < numbers.size(); ++i)
    {
        std::cout << numbers[i].value() << " ";
    }
}
like image 23
Öö Tiib Avatar answered Oct 12 '22 03:10

Öö Tiib


There are two ways of doing that.

The nice, and C++0x way, is to take advantage of initializers list:

std::vector<UniqueNumber> vec = { UniqueNumber(0), UniqueNumber(1) };

Obviously the issue here is that you have to specify them out in full.

In C++03, I would simply use a loop:

std::vector<UniqueNumber> vec;
vec.reserve(10);
for (size_t i = 0; i != 10; ++i) { vec.push_back(UniqueNumber(i)); }

Of course this could perfectly get embedded in a builder function:

template <typename ValueType, typename Generator>
std::vector<ValueType> generateVector(size_t size, Generator generator)
{
  std::vector<ValueType> vec;
  vec.reserve(size);
  for (size_t i = 0; i != size; ++i) { vec.push_back(generator()); }
  return vec;
}

With NRVO kicking in, it's about the same, and it lets you specify the values created freely.

The same can be achieved with the STL generate_n in <algorithm>, and I'll include a feel of lambda syntax.

std::vector<ValueType> vec;
size_t count = 0;
std::generate_n(std::back_inserter(vec), 10, [&]() { return Foo(++count); });

Proposed by @Eugen in the comments :)


Note: wrt to "surprise", if you wish to have unique elements, perhaps that a vector is not the most suitable data structure. And if you actually need a vector, I would consider wrapping into a class to ensure, as an invariant, that elements are unique.

like image 24
Matthieu M. Avatar answered Oct 12 '22 02:10

Matthieu M.


You need to add a copy constructor:

UniqueNumber(const UniqueNumber& un) : mValue(sInstanceCount++)
{ }

The fill constructor of std::vector is not calling your default constructor. Rather, it's calling the default copy constructor which exists implicitly. That constructor of course will not increment your internal static counter variable.

You also need to define an assignment operator as well.

However, using an object that has an internal static counter with an std::vector is going to produce unexpected results, because the vector can internally copy-construct/assign your object as much as it sees fit. So this may interfere with the copy-semantics you require here.

like image 29
Charles Salvia Avatar answered Oct 12 '22 02:10

Charles Salvia