как инициализировать 'const std :: vector <T>' как массив переменного тока

79

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

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

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

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

vscharf
источник

Ответы:

61

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

например:

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

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

для C ++ 11:

vector<int> luggage_combo = { 1, 2, 3, 4, 5 };
Ферруччо
источник
1
«Должен» может быть немного сильным; двухэтапный подход также может работать. Хотя бустовый подход, безусловно, хорош.
janm
Geussing, вы имели в виду: #include <boost / assign / std / vector.hpp>
Зак,
1
@Jason: boost.assign определяет шаблонные перегрузки оператора + = и оператора в векторе <T>
Ферруччо
но как перехватить каждое из значений запятой?
Jason S
41
12345? Такая же комбинация у меня в багаже!
JRG
39

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

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

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

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

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

или же

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

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

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

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

Стив Джессоп
источник
Если я не хочу копировать 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>
AudioDroid
@AudioDroid: да, нет возможности поместить что-либо в вектор, не скопировав его (или переместив, в C ++ 11), поэтому ближе всего вы получите вектор указателей (или интеллектуальных указателей). Но если у вас уже есть три объекта в массиве, то обычно нет смысла создавать на них вектор указателей: просто используйте массив для того, для чего вы бы использовали этот вектор.
Стив Джессоп
15

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

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

Теперь в C ++ 11 есть списки инициализаторов, поэтому вам не нужно делать это таким образом или даже использовать Boost. Но, в качестве примера, вы можете сделать это в C ++ 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;
        }
    }

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

Способ tjohns20, измененный следующим образом, может быть лучшим C ++ 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;
    }
    
}
Тень2531
источник
злой, но полезный для тестирования и т. д. ++
Эли Бендерский
12

Как говорили другие, вы не можете инициализировать вектор так же, как вы можете инициализировать массив в стиле 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);
Джон Диблинг
источник
4
Это хороший момент, но он передает его функции, которая ожидает вектор. Возможно, он не сможет изменить эту функцию.
Ферруччо,
Это требует внешнего отслеживания размера вектора, что является проблемой при работе со многими векторами. В противном случае это решение было бы лучшим, поскольку для него требуется только одна копия целых чисел в памяти.
MasterHD
6

Сделать это можно в два этапа:

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

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

Джанм
источник
5

Как насчет:

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

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

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;
}
Кевин
источник
0

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

vector<T> vec(num_items, item);

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

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

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

Питер
источник
0

Основываясь на ответе 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();

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

tjohns20
источник
0

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

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

мстробл
источник
С этим кодом много проблем. Вы не можете просто вызвать vector :: reserve (), а затем начать манипулировать указателями и в зависимости от деталей магической реализации, таких как степени двойки. Итератор преобразован в указатель? А как насчет vector :: size ()? На каком компиляторе и библиотеке STL это вообще компилируется?
janm
Я согласен, это очень плохой код. Лучшее, вам следует забыть об использовании указателя / итератора и вернуться к push_back
paercebal
Нет, это совершенно законный код на C ++. STL гарантирует, что это сработает: вектор будет выделять последовательное пространство как минимум для 2 ^ n элементов. После всех 2 ^ n элементов vector разрешено перераспределить, что дает вам новое пространство за vector :: begin (). Извините, это может быть хакерство, но это стандарт. :-)
mstrobl
1
... однако я глубоко не убежден, что использование гарантии непрерывной памяти для добавления новых значений по указателю является стандартным - для начала, у вектора нет возможности обновить свой собственный размер, поэтому я почти уверен, что результаты этого кода либо не определены, либо являются вектором размера 0.
Стив Джессоп,
1
Конечно, вы можете использовать непрерывное пространство в векторе; стандарт гарантирует это. Тем не менее, вызова reserve () для этого недостаточно; вы должны добавить элементы так, чтобы size () был правильным. Вы можете изменить код, чтобы выделить правильное количество элементов ....
янм,