C++: инициализатор конструктора для массивов

У меня спазм мозга... как мне правильно инициализировать массив объектов в 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) {}
};

edit: Дикие и безумные идеи обходных путей приветствуются, но в моем случае они мне не помогут. Я работаю над встроенным процессором, где std::vector и другие конструкции STL недоступны, и очевидным обходным путем является создание конструктора по умолчанию и наличие явного метода init(), который можно вызывать после времени создания, так что я не вообще не нужно использовать инициализаторы. (Это один из тех случаев, когда меня испортило ключевое слово Java final + гибкость конструкторов.)


person Jason S    schedule 09.03.2010    source источник
comment
Ваши классы невозможно построить, потому что все private.   -  person John Dibling    schedule 09.03.2010
comment
(ключевые слова доступа опущены для педагогической простоты)   -  person Jason S    schedule 09.03.2010
comment
Не было бы проще использовать struct вместо class для педагогической простоты? Я нахожу код, который компилируется, легче учиться ;-)   -  person Steve Jessop    schedule 09.03.2010
comment
Когда я скопировал ваш код в свой компилятор, мне пришлось добавить то, что вы пропустили. Так что для педагогической простоты вы можете подумать о том, чтобы не мешать людям помогать вам в будущем.   -  person John Dibling    schedule 09.03.2010
comment
Стив/Джон: верно по обоим пунктам. моя вина.   -  person Jason S    schedule 09.03.2010
comment
исправлено (я думаю... у меня нет быстрой и простой среды разработки C++ на моей машине)   -  person Jason S    schedule 09.03.2010
comment
@Jason: Возьми один, это бесценно. Вы также можете использовать codepad.org для кода, подобного этому.   -  person    schedule 11.03.2010


Ответы (13)


Выхода нет. Вам нужен конструктор по умолчанию для элементов массива, и он будет вызываться, после чего вы можете выполнить любую инициализацию в конструкторе.

person AProgrammer    schedule 09.03.2010
comment
К сожалению, ты прав. +1 Обратите внимание, что унифицированный синтаксис инициализации С++ 1x позволит вам это сделать. - person sbi; 09.03.2010
comment
@sbi Если требуемый конструктор не помечен как явный - person user877329; 13.02.2016

Просто чтобы обновить этот вопрос для С++ 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} { }
};
person Barry    schedule 26.06.2015
comment
Есть ли хороший способ инициализировать Foo foo[3][2];? - person dshin; 27.06.2015
comment
@dshin Так же. Максимально закрепленный: : foo{{{1}, {2}}, {{3}, {4}}, {{5}, {6}}}, или менее закрепленный foo{{1, 2}, {3, 4}, {5, 6}}, или наименее закрепленный foo{1, 2, 3, 4, 5, 6}. - person Barry; 27.06.2015
comment
Есть ли обходной путь, когда конструктор Foo объявлен явным? - person dshin; 06.01.2016

Прямо сейчас вы не можете использовать список инициализаторов для элементов массива. Вы застряли, делая это трудным путем.

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}) {}
};
person Michael Kristofik    schedule 09.03.2010
comment
Конструктор с одним аргументом будет вызываться для int, если вы не объявите конструктор явным образом. - person jmanning2k; 09.03.2010
comment
интересно... Вероятно, мне следовало использовать что-то кроме int в моем примере, так как с этим слишком легко иметь дело. :-) - person Jason S; 09.03.2010

К сожалению, нет возможности инициализировать элементы массива до 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 };
person UncleBens    schedule 09.03.2010
comment
+1. Интересно, почему никто не придумал это, пока я не увидел ваш ответ. array тривиально реализовать, и это не дико и не безумно. Вы также можете написать функцию наподобие array<Foo, 3> create() { array<Foo, 3> a = { ... }; return a; }, чтобы избежать статической переменной. - person Johannes Schaub - litb; 09.03.2010
comment
Мне это тоже кажется очевидным, даже если поддержка шаблонов слаба в целевом компиляторе (нет std::vector кажется подозрительным), подход генерации будет работать (препроцессор или скрипт, генерирующий необходимые классы). - person Matthieu M.; 09.03.2010

Вы можете использовать ключевое слово C++0x auto вместе с специализацией шаблона, например, для функции с именем boost::make_array() (аналогично make_pair()). В случае, когда 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);

используя как A, так и B, как это делается для

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

Однако для пользовательских типов (UDT) вариант B приводит к дополнительному конструктору копирования, который обычно замедляет работу, и поэтому его следует избегать.

Обратите внимание, что boost::make_array ошибки при вызове с явными литералами массива char, как в следующем случае

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

Я считаю, что это хорошо, так как const char* литералы могут быть обманчивыми при их использовании.

Шаблоны Variadic, доступные в 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?

person Nordlöw    schedule 15.06.2011
comment
спасибо, но C++0x недоступен на младших встроенных процессорах (компиляторы C++ достаточно сложно найти) - person Jason S; 15.06.2011

Кажется, это работает, но я не уверен, что это правильно:

#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

Пусть покупатель будет бдителен.

Редактировать: нет, Комо отвергает это.

Другое редактирование: это своего рода мошенничество, оно просто перемещает инициализацию массива для каждого члена в другое место. Таким образом, для 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 на самом деле не является массивом, но выглядит так же, как и вектор. Добавление функций begin() и end() в Three тривиально.

person Steve Jessop    schedule 09.03.2010
comment
... и это дает мне некоторые идеи, которые могут подойти для моей ситуации более четко, чем то, что у меня есть. Благодарность! - person Jason S; 09.03.2010

При создании объектов в массиве можно вызывать только конструктор по умолчанию.

person Robert    schedule 09.03.2010

В конкретном случае, когда массив является элементом данных класса, вы не можете инициализировать его в текущей версии языка. Для этого нет синтаксиса. Либо предоставьте конструктор по умолчанию для элементов массива, либо используйте std::vector.

Автономный массив может быть инициализирован агрегатным инициализатором.

Foo foo[3] = { 4, 5, 6 };

но, к сожалению, нет соответствующего синтаксиса для списка инициализаторов конструктора.

person AnT    schedule 09.03.2010

Не существует синтаксиса построения массива, который можно было бы использовать в этом контексте, по крайней мере, напрямую. Вы можете выполнить то, что пытаетесь выполнить, с помощью чего-то вроде:

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 конструктор по умолчанию.

person John Dibling    schedule 09.03.2010

Идеи извращенного ума:

class mytwistedclass{
static std::vector<int> initVector;
mytwistedclass()
{
    //initialise with initVector[0] and then delete it :-)
}

};

теперь установите этот initVector на то, что вы хотите, прежде чем создавать экземпляр объекта. Затем ваши объекты инициализируются с вашими параметрами.

person the100rabh    schedule 09.03.2010

Можно, но некрасиво:

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

Если вы поместите это в свой код, я надеюсь, у вас есть ОЧЕНЬ веская причина.

person Moshev    schedule 29.02.2016

Это мое решение для справки:

struct Foo
{
    Foo(){}//used to make compiler happy!
    Foo(int x){/*...*/}
};

struct Bar
{
    Foo foo[3];

    Bar()
    {
        //initialize foo array here:
        for(int i=0;i<3;++i)
        {
            foo[i]=Foo(4+i);
        }
    }
};
person user7967189    schedule 05.05.2017

в Visual Studio 2012 или выше вы можете сделать так

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

struct Baz { 
     Foo foo[3];

     Baz() : foo() { }
};
person bowman han    schedule 13.01.2016