简体   繁体   中英

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

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?

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() << " ";
    }
}

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM