问题 如何使用size参数初始化std :: vector并使每个对象独立构造?


我喜欢创建具有给定大小和值的向量,例如:

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

然而,这几次导致了意想不到的结果。例如,在以下代码中各自 UniqueNumber 结果是有相同的价值:

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

控制台输出:

0 0 0 0 0 0 0 0 0 0

在查看std :: vector的构造函数时它确实有意义:

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

显然,向量是用同一对象的副本初始化的。

是否还有一种方法可以构建每个对象的默认值?


6327
2018-01-21 18:56


起源



答案:


generate 要么 generate_n 是专门为你想做的事而设计的。这是一个完整的例子:

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

输出: 1 2 3 4 5 6 7 8 9 10


4
2018-01-21 20:02



谢谢,提供非侵入式解决方案。 - StackedCrooked
@StackedCrooked,实际上它是侵入性的,我更喜欢使用一个对象 make_foo,它会好得多。 - Matthieu M.


答案:


generate 要么 generate_n 是专门为你想做的事而设计的。这是一个完整的例子:

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

输出: 1 2 3 4 5 6 7 8 9 10


4
2018-01-21 20:02



谢谢,提供非侵入式解决方案。 - StackedCrooked
@StackedCrooked,实际上它是侵入性的,我更喜欢使用一个对象 make_foo,它会好得多。 - Matthieu M.


矢量是复制构造初始化的元素。

试试看:

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

4
2018-01-21 19:11



我不确定为什么你在UniqueNumber的构造函数中添加了额外的boolean参数,但很明显,你的方法是最安全的(与其他命题相比)。 - Benoît
布尔值是因此用户可以将其构造为唯一或特殊的(一种没有值的)。 - Öö Tiib
创意+1 - StackedCrooked
它很有创意,但使用全局变量是不好的做法;) - Matthieu M.
@Matthieu:是的 private 所以尽管它在全球范围内“可用”,但并非全球性的 无障碍。静态成员通常不被认为是“全局”。 - Lightness Races in Orbit


有两种方法可以做到这一点。

好的和C ++ 0x方式是利用初始化列表:

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

显然,这里的问题是你必须完整地指出它们。

在C ++ 03中,我只是使用一个循环:

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

当然,这可以完美地嵌入到构建器函数中:

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;
}

随着NRVO的推进,它大致相同,它允许您指定自由创建的值。

使用STL也可以实现同样的目的 generate_n 在 <algorithm>,我将包括一种lambda语法的感觉。

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

提议者 @Eugen 在评论:)


注意:wrt要“惊喜”,如果你想拥有独特的元素,或许那个 vector 不是最合适的数据结构。如果你真的需要一个 vector,我会考虑包装成一个类,以确保元素是唯一的不变量。


4
2018-01-21 19:46



打败我,即将提出使用 std::generate_n( std::back_inserter( numbers ), 10, ... ); 同 ... 存在是“常规” CreateUniqueNumber 功能还是lambda ...... - Eugen Constantin Dinca
@Eugen:啊,我曾经简单地想过看过这样的结构,但是不记得它是STL功能了!当然,问题始终是如何从简单中获得 for 一个冗长的表达...... - Matthieu M.
<personal_opinion>我觉得generate_n比一般的更具表现力而且不再啰嗦,特别是在玩lambdas时</ personal_opinion> - Eugen Constantin Dinca
@Eugen:我加了。 - Matthieu M.


您需要添加一个复制构造函数:

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

填充构造函数 std::vector 没有调用默认构造函数。相反,它调用默认值 复制 隐式存在的构造函数。那个构造函数当然不会增加你的内部静态计数器变量。

您还需要定义赋值运算符。

但是,使用具有内部静态计数器的对象 std::vector 会产生意想不到的结果,因为矢量可以在内部复制 - 构造/分配你认为合适的对象。因此,这可能会干扰您在此处所需的复制语义。


3
2018-01-21 19:05



+1你打败了我:) - Zevan
好吧,它调用默认构造函数一次,然后为它添加的每个元素调用默认的复制构造函数。结果可能仍然有点出乎意料,因为如果你没有注意到,这些值将从1开始而不是0 。 - Null Set
这将修复我的样本,但现在副本不再与原始副本相同。我认为这是相当不直观的行为。 - StackedCrooked
这个解决方案有一个问题,当向量填满并且你再推送一个,那么所有的值都会改变。 - Öö Tiib
@ÖöTiib这是真的。实际上,这通常是使用STL容器和副本/赋值语义不只是生成相同克隆的对象的问题。 - Charles Salvia