C++:数组的构造函数初始化器

76

我脑子有点抽...如何在C++中正确地初始化对象数组?

非数组示例:

struct Foo { Foo(int x) { /* ... */  } };

struct Bar { 
     Foo foo;

     Bar() : foo(4) {}
};

数组示例:

struct Foo { Foo(int x) { /* ... */  } };

struct Baz { 
     Foo foo[3];

     // ??? I know the following syntax is wrong, but what's correct?
     Baz() : foo[0](4), foo[1](5), foo[2](6) {}
};

编辑: 我欢迎各种奇思妙想的解决方案,但是它们在我的情况下没有用。我正在处理一个嵌入式处理器,在那儿 std::vector 和其他STL结构不可用,并且明显的解决方法是创建一个默认构造函数并拥有一个显式的 init() 方法,可以在构造后调用,这样我就不必使用初始化程序了。(这是那些被Java的 final 关键字和构造函数灵活性宠坏了的情况之一)。


5
好的,请提供需要翻译的内容。 - Jason S
9
为了简化教学,使用 struct 替代 class 不是更容易吗?我发现编译通过的代码更容易学习 ;-) - Steve Jessop
4
当我将你的代码复制到我的编译器时,我不得不添加你遗漏的部分。因此为了教学的简单性,你可以考虑不要让人们在未来帮助你变得困难。 - John Dibling
1
史蒂夫/约翰:两个都是真的。我错了。 - Jason S
1
@Jason:买一个,它是无价的。你也可以使用http://codepad.org/来编写类似这样的代码。 - Roger Pate
显示剩余2条评论
14个回答

58

编辑:请参考Barry的答案,其中有更近期的内容。在我回答时没有办法,但现在你很少受到C++98的限制。


不可能。对于数组成员,您需要一个默认构造函数,并且它将被调用,之后您可以在构造函数中进行任何初始化。


8
很遗憾,你说得对。+1 注意,C++1x的统一初始化语法将允许你这样做。 - sbi
@sbi 除非所需的构造函数被标记为显式 - user877329

47

仅针对C++11更新此问题,现在这既是可行的,也非常自然:

struct Foo { Foo(int x) { /* ... */  } };

struct Baz { 
     Foo foo[3];

     Baz() : foo{{4}, {5}, {6}} { }
};

这些大括号也可以被省略,以获得更简洁的写法:

struct Baz { 
     Foo foo[3];

     Baz() : foo{4, 5, 6} { }
};

这也可以轻松地扩展到多维数组:

struct Baz {
    Foo foo[3][2];

    Baz() : foo{1, 2, 3, 4, 5, 6} { }
};

有没有一种好的方法来初始化Foo foo [3] [2];? - dshin
3
@dshin 同样的方式。最多括号:: foo{{{1}, {2}}, {{3}, {4}}, {{5}, {6}}},或者较少括号 foo{{1, 2}, {3, 4}, {5, 6}},或者最少括号 foo{1, 2, 3, 4, 5, 6} - Barry
3
Foo的构造函数被声明为explicit时,是否有替代方案? - dshin
这个语法也可以用于对象数组吗? - tickietackie

17

目前,您无法使用初始化列表来初始化数组成员。 您必须采用更麻烦的方式。

class Baz {
    Foo foo[3];

    Baz() {
        foo[0] = Foo(4);
        foo[1] = Foo(5);
        foo[2] = Foo(6);
    }
};

在 C++0x 中,您可以编写:

class Baz {
    Foo foo[3];

    Baz() : foo({4, 5, 6}) {}
};

如果您没有声明构造函数为explicit,则将调用一个参数构造函数来处理int类型。 - jmanning2k
有趣...在我的例子中,我可能应该使用除了 int 以外的东西,因为它太“简单”了。 :-) - Jason S

7

很遗憾,在C++0x之前没有初始化数组成员的方法。

您可以使用std::vector并在构造函数体中使用push_back来添加Foo实例。

您可以为Foo提供一个默认构造函数(可能是私有的,并使Baz成为友元)。

您可以使用可复制的数组对象(boost或std::tr1)并从静态数组初始化:

#include <boost/array.hpp>

struct Baz {

    boost::array<Foo, 3> foo;
    static boost::array<Foo, 3> initFoo;
    Baz() : foo(initFoo)
    {

    }
};

boost::array<Foo, 3> Baz::initFoo = { 4, 5, 6 };

我曾经想过为什么没有人想到这个,直到看到了你的答案。array 很容易实现,它既不疯狂也不奇怪。你可以编写像 array<Foo, 3> create() { array<Foo, 3> a = { ... }; return a; } 这样的函数来避免使用静态变量。 - Johannes Schaub - litb
对我来说也很明显,即使目标编译器对模板的支持较弱(没有 std::vector 看起来有点可疑),生成方法仍然可以使用(预处理器或脚本生成必要的类)。 - Matthieu M.

3

您可以在名为boost::make_array()(类似于make_pair())的函数上,结合C++0xauto关键字和模板特化使用。对于N是1或2个参数的情况,我们可以将变体A表述为:

namespace boost
{
/*! Construct Array from @p a. */
template <typename T>
boost::array<T,1> make_array(const T & a)
{
    return boost::array<T,2> ({{ a }});
}
/*! Construct Array from @p a, @p b. */
template <typename T>
boost::array<T,2> make_array(const T & a, const T & b)
{
    return boost::array<T,2> ({{ a, b }});
}
}

并且将变体B视为

namespace boost {
/*! Construct Array from @p a. */
template <typename T>
boost::array<T,1> make_array(const T & a)
{
    boost::array<T,1> x;
    x[0] = a;
    return x;
}
/*! Construct Array from @p a, @p b. */
template <typename T>
boost::array<T,2> make_array(const T & a, const T & b)
{
    boost::array<T,2> x;
    x[0] = a;
    x[1] = b;
    return x;
}
}

GCC-4.6使用-std=gnu++0x-O3编译时,生成的二进制代码完全相同

auto x = boost::make_array(1,2);

它同时使用AB,就像它对待其它技术一样。

boost::array<int, 2> x = {{1,2}};

对于用户定义类型(UDT),变体B会产生一个额外的复制构造函数,通常会减慢速度,因此应该避免使用。

请注意,在以下情况下使用显式字符数组文字调用boost::make_array时会出现错误:

auto x = boost::make_array("a","b");

我认为这是一件好事,因为const char*字面值在使用时可能会产生误导。

Variadic templates自GCC 4.5以来可用,可以进一步用于将每个N的所有模板专业化样板代码减少到一个boost :: make_array()单个模板定义中。

/*! Construct Array from @p a, @p b. */
template <typename T, typename ... R>
boost::array<T,1+sizeof...(R)> make_array(T a, const R & ... b)
{
    return boost::array<T,1+sizeof...(R)>({{ a, b... }});
}

这个功能基本上符合我们的预期。第一个参数确定了boost::array模板参数T,而所有其他参数都被转换为T。对于某些情况,这可能是不希望的,但我不确定是否可以使用可变模板来指定。

也许boost::make_array()应该加入Boost库中?


谢谢,但C++0x在低端嵌入式处理器上不可用(C++编译器已经很难找了)。 - Jason S

2

这似乎可以工作,但我不确定它是否正确:

#include <iostream>

struct Foo { int x; Foo(int x): x(x) { } };

struct Baz { 
     Foo foo[3];

    static int bar[3];
     // Hmm...
     Baz() : foo(bar) {}
};

int Baz::bar[3] = {4, 5, 6};

int main() {
    Baz z;
    std::cout << z.foo[1].x << "\n";
}

输出:

$ make arrayinit -B CXXFLAGS=-pedantic && ./arrayinit
g++ -pedantic    arrayinit.cpp   -o arrayinit
5

买家自负。

编辑:不行,Comeau拒绝它。

另一个编辑:这有点作弊,它只是将逐个成员数组初始化推到了不同的位置。因此,它仍然需要Foo具有默认构造函数,但如果您没有std::vector,则可以为自己实现绝对最少所需:

#include <iostream>

struct Foo { 
    int x; 
    Foo(int x): x(x) { }; 
    Foo(){}
};

// very stripped-down replacement for vector
struct Three { 
    Foo data[3]; 
    Three(int d0, int d1, int d2) {
        data[0] = d0;
        data[1] = d1;
        data[2] = d2;
    }
    Foo &operator[](int idx) { return data[idx]; }
    const Foo &operator[](int idx) const { return data[idx]; }
};

struct Baz { 
    Three foo;

    static Three bar;
    // construct foo using the copy ctor of Three with bar as parameter.
    Baz() : foo(bar) {}
    // or get rid of "bar" entirely and do this
    Baz(bool) : foo(4,5,6) {}
};

Three Baz::bar(4,5,6);

int main() {
    Baz z;
    std::cout << z.foo[1].x << "\n";
}

z.foo 实际上不是一个数组,但它的外观与向量非常相似。在Three中添加 begin()end() 函数非常简单。


...这让我有了一些想法,可能比我当前的情况更清晰地解决问题。谢谢! - Jason S

2
在特定情况下,当数组是类的数据成员时,您无法在当前版本的语言中初始化它。没有相应的语法。您可以为数组元素提供默认构造函数或使用std::vector。
独立数组可以使用聚合初始化器进行初始化。
Foo foo[3] = { 4, 5, 6 };

但不幸的是,构造函数初始化列表没有对应的语法。


1

创建数组对象时只能调用默认构造函数。


0

你可以这么做,但这不太美观:

#include <iostream>

class A {
    int mvalue;
public:
    A(int value) : mvalue(value) {}
    int value() { return mvalue; }
};

class B {
    // TODO: hack that respects alignment of A.. maybe C++14's alignof?
    char _hack[sizeof(A[3])];
    A* marr;
public:
    B() : marr(reinterpret_cast<A*>(_hack)) {
        new (&marr[0]) A(5);
        new (&marr[1]) A(6);
        new (&marr[2]) A(7);
    }

    A* arr() { return marr; }
};

int main(int argc, char** argv) {
    B b;
    A* arr = b.arr();
    std::cout << arr[0].value() << " " << arr[1].value() << " " << arr[2].value() << "\n";
    return 0;
}

如果你在代码中使用这个,希望你有非常充分的理由。

0

在这种情况下,没有可以直接使用的数组构造语法。你可以通过以下方式实现你想要达到的目的:

Bar::Bar()
{
    static const int inits [] = {4,5,6};
    static const size_t numInits = sizeof(inits)/sizeof(inits[0]);
    std::copy(&inits[0],&inits[numInits],foo);  // be careful that there are enough slots in foo
}

...但你需要给Foo一个默认构造函数。


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