как инициализировать 'const std::vector‹T›' как массив c

Есть ли элегантный способ создать и инициализировать const std::vector<const T> как const T a[] = { ... } фиксированным (и небольшим) числом значений?
Мне нужно часто вызывать функцию, которая ожидает vector<T>, но эти значения никогда не изменятся в моем случае.

В принципе, я думал о чем-то вроде

namespace {
  const std::vector<const T> v(??);
}

поскольку v не будет использоваться вне этой единицы компиляции.


person vscharf    schedule 23.10.2008    source источник


Ответы (10)


Вам либо нужно дождаться C++0x, либо использовать что-то вроде Boost.Assign для этого.

e.g.:

#include <boost/assign/std/vector.hpp>
using namespace boost::assign; // bring 'operator+=()' into scope

vector<int> v;
v += 1,2,3,4,5;

для С++ 11:

vector<int> luggage_combo = { 1, 2, 3, 4, 5 };
person Ferruccio    schedule 23.10.2008
comment
Должен быть немного сильным; двухэтапный подход также может работать. Хотя буст подход конечно хорош. - person janm; 24.10.2008
comment
Полагаю, вы имели в виду: #include ‹boost/assign/std/vector.hpp› - person Zac; 02.06.2010
comment
@Jason: boost.assign определяет шаблонные перегрузки оператора+= и оператора для вектора‹T› - person Ferruccio; 31.01.2011
comment
но как вы перехватываете каждое из значений запятой? - person Jason S; 31.01.2011
comment
не 1,2,3,4,5 оценивается как 5? - person Jason S; 31.01.2011
comment
@Jason: Обычно да. Но запятая — это оператор, и его можно перегрузить, чтобы сделать что-то еще, как и большинство других операторов. Я не знаю подробностей того, как это реализует boost, но я могу представить, как можно определить оператор запятой, чтобы взять список чисел и одно число и вернуть список, который объединяет их. Под списком я не подразумеваю std::list, я использую термин в общем. - person Ferruccio; 01.02.2011
comment
Каков подход к C++0x? - person galymzhan; 31.07.2011
comment
с C++0x вы можете использовать: vector‹int› c = { 1, 2, 3, 4, 5 }; - person Ferruccio; 01.08.2011
comment
12345? У меня такая же комбинация в багаже! - person JRG; 06.08.2011

Если вы спрашиваете, как инициализировать константный вектор, чтобы он имел интересное содержимое, то ответ, вероятно, будет заключаться в использовании конструктора копирования. Сначала вы кропотливо заполняете вектор, а затем создаете из него новый постоянный вектор. Или вы можете использовать шаблон конструктора vector<InputIterator>(InputIterator, InputIterator) для инициализации из другого типа контейнера или массива. Если это массив, то его можно было бы определить с помощью списка инициализации.

Надеюсь, что-то вроде этого близко к тому, что вы хотите:

const T ra[3] = {t1, t2, t3};
const vector<const T> v(ra, ra+3);

Если вы спрашиваете, как передать константный вектор в функцию, которая принимает вектор, то ответ будет либо:

  • вы не можете, потому что функция может изменить вектор, а ваш объект/ссылка является константой. Сделайте непостоянную копию оригинала и передайте ее.

or

  • используйте const_cast, чтобы удалить константу, чтобы передать ее в функцию, которая принимает неконстантный вектор, но которая, как вы случайно знаете, не изменит вектор.

Последнее — одна из тех вещей, которые совершенно справедливо заставят любого, кто увидит это, комментировать очки и тот факт, что они ничего не делают. Это именно то, для чего нужен const_cast, но есть достаточно веский аргумент, говорящий, что если вам нужен const_cast, вы уже проиграли.

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

[Редактировать: только что заметил, что вы говорите о разнице между vector<T> и const vector<const T>. К сожалению, в STL типы vector<const T> и vector<T> совершенно не связаны друг с другом, и единственный способ преобразования между ними — копирование. В этом разница между векторами и массивами — T** можно тихо и безопасно преобразовать в const T *const *]

person Steve Jessop    schedule 23.10.2008
comment
Если я не хочу копировать t1, t2, t3, как я могу сделать это лучше всего? Это единственное/лучшее решение, которое я нашел до сих пор: ‹code›const int *p = &ra[0]; вектор‹const int *› v; for(int i = 0; i ‹ size; ++i, ++p) v.push_back(p);‹/code› - person AudioDroid; 16.05.2012
comment
@AudioDroid: да, невозможно поместить что-либо в вектор без его копирования (или перемещения в C++ 11), поэтому самое близкое, что вы получите, - это вектор указателей (или интеллектуальных указателей). Но если у вас уже есть три объекта в массиве, то обычно нет смысла создавать вектор указателей на них: просто используйте массив для того, для чего вы бы использовали вектор. - person Steve Jessop; 16.05.2012

Короткий и грязный путь (похож на list_of() Boost)

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
using namespace std;

template <typename T>
struct vlist_of : public vector<T> {
    vlist_of(const T& t) {
        (*this)(t);
    }
    vlist_of& operator()(const T& t) {
        this->push_back(t);
        return *this;
    }
};

int main() {
    const vector<int> v = vlist_of<int>(1)(2)(3)(4)(5);
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n"));
}

Теперь в С++ 11 есть списки инициализаторов, поэтому вам не нужно делать это таким образом или даже использовать Boost. Но, например, вы можете сделать вышеописанное на С++ 11 более эффективно, например так:

    #include <iostream>
    #include <vector>
    #include <utility>
    #include <ostream>
    using namespace std;

    template <typename T>
    struct vlist_of : public vector<T> {
        vlist_of(T&& t) {
            (*this)(move(t));
        }
        vlist_of& operator()(T&& t) {
            this->push_back(move(t));
            return *this;
        }
    };

    int main() {
        const vector<int> v = vlist_of<int>(1)(2)(3)(4)(5);
        for (const auto& i: v) {
            cout << i << endl;
        }
    }

Но это все еще не так эффективно, как использование списка инициализаторов С++ 11, потому что для вектора не определено operator=(vlist_of&&).

способ tjohns20 изменен следующим образом, может быть лучше С++ 11 vlist_of:

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

template <typename T>
class vlist_of {
    public:
        vlist_of(T&& r) {
            (*this)(move(r));
        }
        vlist_of& operator()(T&& r) {
            v.push_back(move(r));
            return *this;
        }
        vector<T>&& operator()() {
            return move(v);
        }
    private:
        vector<T> v;
    
};

int main() {
    const auto v = vlist_of<int>(1)(2)(3)(4)(5)();
    for (const auto& i : v) {
        cout << i << endl;
    }
    
}
person Shadow2531    schedule 31.10.2008
comment
злой, но полезный для тестирования и т. д. ++ - person Eli Bendersky; 08.05.2010

Как уже говорили другие, вы не можете инициировать вектор так же, как вы можете инициировать массив в стиле C, если только вы не дадите ему указатели на исходный массив. Но в этом случае, если ваш вектор является глобальной константой, почему бы просто не использовать вместо этого старый массив в стиле C?

const int MyInts[] = {
1, 2, 3, 4, 5};

const size_t NumMyInts = sizeof(MyInts)/sizeof(MyInts[0]);

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

const int* myInt = std::find( &MyInts[0], &MyInts[NumMyInts], 3);
person John Dibling    schedule 23.10.2008
comment
Это хороший момент, но он передает его функции, которая ожидает вектор. Возможно, он не сможет изменить эту функцию. - person Ferruccio; 24.10.2008
comment
Это требует внешнего отслеживания размера вектора, что является проблемой при работе со многими векторами. В противном случае это решение было бы лучшим, поскольку для него требуется только одна копия целых чисел в памяти. - person MasterHD; 14.07.2016

Вы можете сделать это в два этапа:

namespace {
    const T s_actual_array[] = { ... };
    const std::vector<const T> s_blah(s_actual_array,
        s_actual_array + (sizeof(s_actual_array) / sizeof(s_actual_array[0])));
}

Возможно, не так красиво, как хотелось бы, зато функционально.

person janm    schedule 23.10.2008

Как насчет:

int ar[]={1,2,3,4,5,6};
const int TotalItems = sizeof(ar)/sizeof(ar[0]);
std::vector<int> v(ar, ar+TotalItems);
person opal    schedule 04.05.2012
comment
Неважно, Стив Джессоп уже указал на этот подход. - person opal; 04.05.2012

Старый вопрос, но сегодня я столкнулся с той же проблемой, вот подход, который был наиболее приемлемым для моих целей:

vector<int> initVector(void)
{
    vector<int> initializer;
    initializer.push_back(10);
    initializer.push_back(13);
    initializer.push_back(3);
    return intializer;
}

int main()
{
    const vector<int> a = initVector();
    return 0;
}

Пример, чтобы избежать чрезмерного копирования:

vector<int> & initVector(void)
{
    static vector<int> initializer;
    if(initializer.empty())
    {
        initializer.push_back(10);
        initializer.push_back(13);
        initializer.push_back(3);
    }
    return intializer;
}

int main()
{
    const vector<int> & a = initVector();
    return 0;
}
person Kevin    schedule 14.01.2014

Если они все одинаковые, вы можете просто сделать

vector<T> vec(num_items, item);

но я предполагаю, что это не так, и в этом случае, вероятно, самый аккуратный способ:

vector<T> vec(num_items);
vec[0] = 15;
vec[1] = 5;
...

C++0x позволит вам использовать список инициализаторов именно так, как вы думаете, но, к сожалению, сейчас это не очень хорошо.

person Peter    schedule 23.10.2008

Основываясь на ответе Shadow2531, я использую этот класс для инициализации векторов без фактического наследования от std::vector, как это сделало решение Shadow

template <typename T>
class vector_init
{
public:
    vector_init(const T& val)
    {
        vec.push_back(val);
    }
    inline vector_init& operator()(T val)
    {
        vec.push_back(val);
        return *this;
    }
    inline std::vector<T> end()
    {
        return vec;
    }
private:
    std::vector<T> vec;
};

Использование:

std::vector<int> testVec = vector_init<int>(1)(2)(3)(4)(5).end();

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

person tjohns20    schedule 09.05.2011

Не уверен, что правильно вас понял. Я понимаю ваш вопрос так: вы хотите инициализировать вектор большим количеством элементов. Что не так с использованием push_back() в векторе? :-)

Если вы знаете количество сохраняемых элементов (или уверены, что он будет хранить меньше следующей степени двойки), вы можете сделать это, если у вас есть вектор указателей типа X (работает только с указателями):

std::vector< X* > v;
v.reserve(num_elems);
X* p = v.begin();
for (int count = 0; count < num_elems; count++)
   p[count] = some_source[count];

Остерегайтесь добавлять элементы, превышающие следующую степень двойки, даже при использовании push_back(). Тогда указатели на v.begin() будут недействительны.

person mstrobl    schedule 23.10.2008
comment
С этим кодом много проблем. Вы не можете просто вызвать vector::reserve(), а затем начать манипулировать указателями и зависеть от магических деталей реализации, таких как степень двойки. Итератор преобразован в указатель? А как насчет vector::size()? На каком компиляторе и библиотеке STL это вообще компилируется? - person janm; 24.10.2008
comment
Я согласен, что это очень плохой код. В лучшем случае вам следует забыть об использовании указателя/итератора и вернуться к push_back - person paercebal; 24.10.2008
comment
Нет, это совершенно законный код C++. STL гарантирует, что это сработает: вектор будет выделять последовательное пространство как минимум для 2^n элементов. После того, как все 2 ^ n элементов vector могут быть перераспределены, вы получите новое пространство за vector::begin(). Извините, это может быть хакерским, но это стандарт. :-) - person mstrobl; 24.10.2008
comment
Я вижу отрицательные голоса, но настаиваю на том, что существуют веские причины для использования этого свойства STL (оно есть по какой-то причине). Эй, есть много докторских диссертаций, в которых используется это свойство :) - person mstrobl; 24.10.2008
comment
Извините, как именно: X* p = v.begin(); стандарт? Как используется оператор [] для доступа к элементам за пределами границ в векторном стандарте? Что такого особенного в указателях, что позволяет работать с (скажем) целыми числами? Где этот код вообще компилируется? - person janm; 24.10.2008
comment
Джанм, STL определяет это поведение как стандартное поведение, нравится вам это или нет. Идите вперед и попробуйте свой компилятор C (включая отсутствующую звездочку :). Это полезно, если вам нужна мощь std::vector и вам нужно, чтобы данные указывались последовательно, например, при отправке данных вершины на графическую карту. - person mstrobl; 24.10.2008
comment
Только что пробовал на VC2005 и gcc 3.4.6. Ошибка, первая ошибка на X** p = v.begin(); Вы должны изменить его на X** p = &v[0]; чтобы даже заставить его скомпилировать. Он все еще сломан; что вы ожидаете от v.size()? - person janm; 24.10.2008
comment
@janm: ты неправильно скопировал его код. Он читается как Xp=, а не X*p=. @mstrobl: степень двойки не входит в стандарт (по крайней мере, не в 23.2.4), но использование резерва для обеспечения того, чтобы начало оставалось действительным, входит в стандарт (23.2.4.2 (5)). Я не уверен, что преобразование vector‹T›::iterator в T* является стандартным, но &*v.begin() — это T*. - person Steve Jessop; 24.10.2008
comment
... однако я глубоко не убежден, что использование гарантии непрерывной памяти для добавления новых значений по указателю является стандартным - для начала, у вектора нет возможности обновить свой собственный размер, поэтому я вполне уверен, что результаты этого кода либо не определены, либо являются вектором размера 0. - person Steve Jessop; 24.10.2008
comment
... вы можете использовать гарантию непрерывной памяти для сброса существующих значений по указателю. 23.2.4(1) гарантирует, что &v[n] == &v[0]+n для 0 ‹= n ‹ v.size(). Он ничего не говорит о v.size() ‹= n ‹ v.capacity(). - person Steve Jessop; 24.10.2008
comment
... так что ваш странный выбор цикла, вероятно, будет действительным, если вы инициализируете с помощью v(num_elems) (конструктор (size_type)) вместо конструктора без аргументов, за которым следует вызов резерва. Но тогда вы могли бы просто установить цикл v[count] вместо того, чтобы возиться с p. - person Steve Jessop; 24.10.2008
comment
Я только что проверил с помощью GCC, и vector‹int*›::iterator не конвертируется ни в int* (как утверждает код mstrobl), ни в int** (как janm думал, что он хотел сказать). &*v.begin() - это int**, как я и ожидал, поэтому, очевидно, его нельзя преобразовать в int*. - person Steve Jessop; 24.10.2008
comment
Всем спасибо за защиту! Я написал это очень поздно ночью вчера. Инициализация была запрошена вопросом, как я его интерпретировал. И это правда, что это некрасиво, но опять же, опоздал. Обычный способ, конечно, был бы наоборот, используя push_back, а затем беря [contd] - person mstrobl; 24.10.2008
comment
[продолжение] преимущество линейного пространства - многие этого не знают. Onebyone, очевидно, делает, и это приятно видеть. Жаль, что Джанм захочет просто отказаться от моего ответа. Что касается силы двух: sgi.com/tech/stl/FAQ. html - person mstrobl; 24.10.2008
comment
Конечно, вы можете воспользоваться преимуществом непрерывного пространства в векторе; Стандарт гарантирует это. Однако для этого недостаточно вызвать backup(); вы должны добавить элементы так, чтобы size() был правильным. Вы могли бы изменить код, чтобы выделить правильное количество элементов.... - person janm; 24.10.2008
comment
... в конструкторе векторов, например, vector‹T› v(n), а затем используйте T* p = &v[0], это было бы нормально. Проблемы, с которыми я столкнулся в этом примере: (1) вызов backup() вместо вызова функции, которая изменяет size(), (2) попытка привести итератор к указателю... - person janm; 24.10.2008
comment
... и (3) утверждение, что это работает только для контейнера с указателями. - person janm; 24.10.2008
comment
Я действительно сделал несколько ошибок в своей реализации. Как я уже писал, было поздно, и я пытался ответить на вопрос, который, как я понял, задавал именно об этом. Я считал, что вектор уже имеет достаточный размер, но не указывал на это. замечание Джанма (3) указывает на .. - person mstrobl; 24.10.2008
comment
... я сделал ошибку. Что касается (2), это не было моей целью. (&(T&)) std::vector‹T›::begin() будет указывать на первый элемент вектора, что и было моим намерением. Код может серьезно отсутствовать в его реализации, и я, возможно, допустил ошибки, но по своей сути он неплох, и в нем нет абсурда. - person mstrobl; 24.10.2008
comment
... хакерское вуду в нем. Нет никакого странного волшебного числа 2^n в качестве комментария, который, кажется, я помню, который теперь уже привык оплакивать. Так что, также видя разгоревшуюся дискуссию, я не понимаю, как можно отмести этот пример просто как плохой код. Я по-прежнему настаиваю на том, что -1 необоснованно. - person mstrobl; 24.10.2008
comment
Кстати, тот SGI FAQ относится только к их реализации. Могут быть и другие реализации STL, в которых используется константа, отличная от 2, поэтому полагаться на нее нестандартно. Кроме того, он только говорит, что каждый раз удваивает емкость: если он начинается с или зарезервирован () для не-мощности, то он останется не-мощностью. - person Steve Jessop; 24.10.2008
comment
Я настоятельно рекомендую всем, кто хочет написать переносимый C++, взять копию стандарта — это избавит вас от предварительной подготовки всего, что вы делаете с этим, работает на GCC 4.4 для x86 linux с SGI и задается вопросом, означает ли это, что он будет работать и в других местах: — ) - person Steve Jessop; 24.10.2008
comment
О, и я говорю константа, отличная от 2, потому что вектор гарантирует амортизированное сложение за постоянное время, и я не знаю другого способа сделать это, кроме как умножать емкость на некоторую константу каждый раз, когда она расширяется. Но константа не обязательно должна быть 2. - person Steve Jessop; 24.10.2008
comment
Конечно, -1 оправдано. Единственное правильное утверждение в ответе: Что не так с использованием push_back() для вектора? Конечно, количество элементов для хранения известно (это константа!), а степень двойки не имеет значения (и имп. деталь). Остальная часть примера просто неверна. - person janm; 26.10.2008
comment
В ответ на мой первый комментарий, где я указал на преобразование итератора в указатель и на проблему с size() и степенями двойки, являющимися деталями реализации, вы ответили: Нет, это совершенно законный код C++. Тот факт, что вы можете получить доступ к памяти в уже выделенном векторе... - person janm; 26.10.2008
comment
... использование указателей очень мало связано с выделением вектора из констант (т. Е. Ответом на фактический вопрос). И ваш код даже этого не делал. Я думаю, тебе повезло, что у тебя только -1. - person janm; 26.10.2008
comment
@onebyone: я сделал T ** из-за комментария от mstrobl: Давай, попробуй свой компилятор C (включая отсутствующую звездочку :), что действительно было единственным способом приблизиться. Я согласен с вашими комментариями. Преобразование vector‹T›::iterator в T* не является стандартным. Указатель на deref'd iter отличается. - person janm; 26.10.2008