як ініціалізувати 'const std :: vector <T>' як масив змінного струму


79

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

В принципі я думав про щось подібне

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

оскільки v не буде використовуватися поза цією одиницею компіляції.

Відповіді:


61

Вам доведеться дочекатися C ++ 0x або використовувати щось на зразок Boost. Призначте це для цього.

наприклад:

#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>
Zac

1
@Jason: boost.assign визначає шаблонні перевантаження оператора + = та оператора, на вектор <T>
Ферруччо

але як ви перехоплюєте кожне зі значень коми?
Jason S

41
12345? У мене така ж комбінація в моєму багажі!
JRG

39

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

Щось подібне, сподіваємось, близьке до того, що ви хочете:

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

Якщо ви запитуєте, як передати вектор const у функцію, яка приймає вектор, тоді відповідь:

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

або

  • використовуйте const_castдля видалення constness, щоб передати його у функцію, яка приймає вектор, який не є const, але про який ви знаєте, не буде змінювати вектор.

Останнє - одна з тих речей, яка, цілком справедливо, змусить кожного, хто бачить це, коментувати окуляри та той факт, що вони нічого не роблять. Це саме те const_cast, для чого, але є досить вагомий аргумент, який говорить, що якщо вам потрібно const_cast, ви вже програли.

Робити обидві ці речі (створювати вектор const з неконстантного за допомогою конструктора копіювання, а потім відкидати constness), безумовно, неправильно - вам слід було просто використовувати вектор, який не є const. Тож виберіть щонайбільше одне з них, щоб зробити ...

[ Редагувати: щойно помітив, що ви говорите про різницю між 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's 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;
    }
    
}

злий, але корисний для тестування тощо. ++
Елі Бендерський

12

Як сказали інші, ви не можете ініціювати вектор так само, як і ініціювати масив у стилі С, якщо тільки ви не надасте йому вказівники на вихідний масив. Але в такому випадку, якщо ваш вектор є глобальним const, чому б просто не використати старий масив у стилі С?

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

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

Ви навіть можете використовувати алгоритми STL проти цього масиву, так само, як і алгоритми проти вектора const ...

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

Не зважаючи на це, "Стів Джессоп" вже підкреслює цей підхід.
опал

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();

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


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

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


З цим кодом багато проблем. Ви не можете просто викликати vector :: reserve (), а потім почати маніпулювати покажчиками і залежно від деталей реалізації магії, таких як повноваження двох. Ітератор перетворений на покажчик? А як щодо vector :: size ()? На якому компіляторі та бібліотеці STL це взагалі компілюється?
janm

Я згоден, що це дуже поганий код. Найкраще, вам слід забути використання вказівника / ітератора та резервного копіювання для push_back
paercebal

Ні, це цілком законний код С ++. STL гарантує, що це спрацює: вектор виділить послідовний простір принаймні для 2 ^ n елементів. Врешті-решт 2 ^ n елементів вектор дозволено перерозподіляти, даючи вам новий простір позаду vector :: begin (). На жаль, це може бути хакі, але це стандарт. :-)
mstrobl

1
... однак я глибоко не впевнений, що використання суміжної гарантії пам'яті для додавання нових значень за допомогою покажчика є стандартним - для початківців вектор не має можливості оновити власний розмір, тому я впевнений, що результати цього коду або невизначені, або вектор розміром 0.
Стів Джессоп,

1
Звичайно, ви можете скористатися суміжним простором у векторі; стандарт гарантує, що. Однак виклику резерву () недостатньо для цього; Ви повинні додати елементи так, щоб size () був правильним. Ви можете змінити код так, щоб виділити правильну кількість елементів ....
janm,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.