如何初始化静态向量成员?

6
例如
struct A
{
    static vector<int> s;
};

vector<int> A::s = {1, 2, 3};

然而,我的编译器不支持初始化列表。有什么简单的实现方法吗?使用lambda函数能帮助解决这个问题吗?
5个回答

4
我总是担心在这种问题中被击落,因为它涉及到初始化顺序,但是请注意,..
#include <iostream>
#include <vector>
#include <iterator>

struct A
{
    static std::vector<int> s;
};

static const int s_data[] = { 1,2,3 };
std::vector<int> A::s(std::begin(s_data), std::end(s_data));

int main()
{
    std::copy(A::s.begin(), A::s.end(), 
              std::ostream_iterator<int>(std::cout, " "));
    return 0;
}

输出

1 2 3

仅仅因为你“能够”并不意味着你“应该” =P

获得“最低效率方法”的奖项:

#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;

template<typename T>
std::vector<T> v_init(const T& t)
{
    return std::vector<T>(1,t);
}

template<typename T, typename... Args>
std::vector<T> v_init(T&& t, Args&&... args)
{
    const T values[] = { t, args... };
    std::vector<T> v1(std::begin(values), std::end(values));
    return v1;
}

struct A
{
    static std::vector<int> s;
};

std::vector<int> A::s(v_init(1,2,3,4,5));


int main(int argc, const char *argv[])
{
    std::copy(A::s.begin(), A::s.end(), std::ostream_iterator<int>(std::cout, " "));
    return 0;
}

输出

1 2 3 4 5 

如果 T 和 Args... 中的任何内容不符合类型标准或不能进行类型转换,则此代码应在编译时失败。当然,如果您具有可变参数模板,则很可能也具有初始化列表,但即使没有,它也可以成为一种有趣的脑力食品。


4

有没有简单的实现方法?

没有特别优雅的方法。你可以从静态数组中复制数据,或者使用函数调用的结果进行初始化。前者可能会使用比你想要的更多的内存,而后者需要一些稍微混乱的代码。

Boost有一个可以使它看起来稍微不那么丑陋:

#include <boost/assign/list_of.hpp>
vector<int> A::s = boost::assign::list_of(1)(2)(3);

lambda函数能在这里帮忙吗?

是的,它可以帮助你避免为初始化向量命名函数:

vector<int> A::s = [] {
    vector<int> v;
    v.push_back(1);
    v.push_back(2); 
    v.push_back(3);
    return v;
}();

严格来说,这个应该有一个明确的返回类型,[]()->vector<int>,因为lambda体中不仅包含一个return语句。一些编译器将接受我的版本,我相信它会在2014年成为标准。


boost::list_of 很棒。但我必须使用 boost::assign::list_of 才能让它工作。实际上,我的向量是 vector<unique<int>>。以下代码是否没有内存泄漏?vector<unique_ptr<int>> A::s = boost::assign::list_of(new int(2)); - user1899020
@user1899020:抱歉,我忘记了assign部分;自从出现花括号初始化以来,我就没有使用过那个库。 - Mike Seymour
1
@user1899020:只要编译成功且没有分配失败,unique_ptr的初始化应该是没问题的。由于这是初始化静态对象,如果失败将会结束程序,因此无需特别担心泄漏问题。更一般地,您应该在任何可能抛出异常之前立即使用make_unique函数来包装每个裸指针。在C++14中应该有一个std::make_unique函数。 - Mike Seymour

3
编写一个简单的向量初始化函数:
vector<int> init()
{
  vector<int> v;
  v.reserve(3);
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  return v;
};

vector<int> A::s = init();

这里到处都是未定义的行为(更不用说它无法编译)。 - James Kanze
@James,看起来我忘记写了两行代码。已经修复并进行了优化。 - rubenvb
不是最简单也不是最优的方法,但是可以接受。(最简单的方法是定义一个静态C风格数组,并将其作为参数传递给vector的两个迭代器构造函数中的begin和end。) - James Kanze
@James 两种解决方案都将元素复制到运行时分配的向量中。如果没有constexprinitializer_list构造函数,我们无法做太多事情... - rubenvb

1
你可以使用两个指针初始化一个 std::vector
int xv[] = {1,2,3,4,5,6,7,8,9};
std::vector<int> x(xv, xv+(sizeof(xv)/sizeof(xv[0])));

你甚至可以在模板函数中将其分解出来:
template<typename T, int n>
std::vector<T> from_array(T (&v)[n]) {
    return std::vector<T>(v, v+n);
}

正式来说,您的初始化表达式具有未定义的行为。&xv[sizeof(xv)/sizeof(xv[0])]是越界访问。实际上,它可能会工作。但为什么要这么复杂:std::vector<int> x( std::begin( xv ), std::end( xv ) );。(当然,如果您没有使用C++11,因为否则,您将使用新样式的初始化程序。因此,实际上,您将不得不使用来自工具包的std::beginstd::end的等效项。) - James Kanze
@Kanze:不对。表达式不是xv[sizeof...],而是&xv[sizeof...]。如果p是一个指针,那么表达式&*p并没有解引用指针。关于为什么int x[10]; &x[10];是合法的,请参见https://dev59.com/RHNA5IYBdhLWcg3wX8rk#988220(包括标准的引用)。 - 6502
表达式 xv[sizeof...] 具有未定义的行为。在未定义的行为之后你所做的事情是无关紧要的。C 有一个特殊规则允许这样做;C++ 委员会考虑将这个特殊规则添加到 C++ 中,但最终决定不这样做。你引用的答案是错误的。 - James Kanze
@JamesKanze:我从来没有想过&*p中的取地址操作会发生在解引用之后,但实际上很显然,在解引用之前你需要地址,这就是为什么常识会说&xv[size]不会有问题,即使在xv[size]中可能存在问题(因为添加&意味着执行的操作更少,具体而言就是不需要访问内存)。我相信你所说的C++在这方面比C 故意 更糟糕(我试着读标准,但我更喜欢解谜游戏)。我已经修正了答案。 - 6502
常识并不总是适用于标准:-)。C90(因此也包括C++)被精心编写以允许边界检查实现,并允许编译器对*p进行边界检查。C语言后来为&*p做了一个特殊的例外。C++没有跟随,可能是因为如果p实际上是一个用户定义类型,具有用户定义的operator*,那么就无法允许它。 - James Kanze
显示剩余5条评论

0

另一个想法:

struct A
{
  static std::vector<int> s;
};

std::vector<int> A::s;

static bool dummy((A::s.push_back(1), A::s.push_back(2), A::s.push_back(3), false));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接