Могу ли я использовать константу в векторах, чтобы добавлять элементы, но не изменять уже добавленные?
Мои комментарии к этому ответу заставили меня задуматься о проблемах состроения и сортировки. Я немного поиграл и уменьшил свои проблемы до того, что этот код:
#include <vector>
int main() {
std::vector <const int> v;
}
не будет компилироваться - вы не можете создать вектор const int. Очевидно, я должен был знать это (и интеллектуально я это сделал), но мне никогда не приходилось создавать такую вещь раньше. Однако мне кажется, что это полезная конструкция, и я задаюсь вопросом, существует ли какой-либо путь вокруг этой проблемы - Я хочу добавить вещи к вектору (или что-то еще), но они не должны меняться после добавления.
Наверное, это довольно сложное решение, но я никогда не думал об этом раньше.
Вероятно, я не должен был упоминать сортировку (я могу задать еще один вопрос об этом, см. этот в связи с трудностями задавать вопросы). Мой реальный случай использования базы - это примерно так:
vector <const int> v; // ok (i.e. I want it to be OK)
v.push_back( 42 ); // ok
int n = v[0]; // ok
v[0] = 1; // not allowed
Ответы
Ответ 1
Ну, в С++ 0x вы можете...
В С++ 03 есть параграф 23.1 [lib.containers.requirements]/3, в котором говорится, что
Тип объектов, хранящихся в этих компонентах, должен удовлетворять требованиям типов CopyConstructible
(20.1.3) и дополнительным требованиям типов Assignable
.
Это то, что в настоящее время не позволяет использовать const int
как аргумент типа std::vector
.
Однако в С++ 0x этот абзац отсутствует, вместо этого T
требуется Destructible
и дополнительно требования к T
указаны для каждого выражения, например v = u
on std::vector
действует только в том случае, если T
MoveConstructible
и MoveAssignable
.
Если я правильно интерпретирую эти требования, должно быть возможно создать экземпляр std::vector<const int>
, вам просто не хватает некоторых его функциональных возможностей (что, я думаю, это то, что вы хотели). Вы можете заполнить его, передав пару итераторов конструктору. Я думаю, что emplace_back()
должен работать, хотя я не нашел явных требований для T
для него.
Вы все равно не сможете сортировать вектор на месте.
Ответ 2
Типы, которые вы помещаете в стандартный контейнер, должны быть скопируемыми и присваиваемыми. Причина, по которой auto_ptr
вызывает столько проблем, заключается в том, что она не соответствует нормальной семантике копирования и присваивания. Естественно, все, что const
не будет назначаемым. Таким образом, вы не можете вставлять const
в стандартный контейнер. И если элемент не const
, то вы сможете его изменить.
Ближайшее решение, которое, я полагаю, возможно, будет состоять в использовании какой-либо косвенности. Таким образом, у вас может быть указатель на const или у вас может быть объект, который содержит требуемое значение, но значение не может быть изменено внутри объекта (например, вы получили бы с Integer
в Java).
Наличие элемента в определенном индексе неизменным противоречит тому, как работают стандартные контейнеры. Возможно, вы сможете создать свои собственные, которые работают таким образом, но стандартные - нет. И ни один из них, основанный на массивах, не будет работать независимо от того, если вы не сможете установить их инициализацию в синтаксис инициализации {a, b, c}
, поскольку после создания массива из const
вы не сможете его изменить. Таким образом, класс vector
вряд ли будет работать с элементами const независимо от того, что вы делаете.
Наличие const
в контейнере без какой-либо косвенности просто не очень хорошо работает. Вы в основном просите сделать весь контейнер const
- который вы можете сделать, если вы скопируете его из уже инициализированного контейнера, но вы действительно не можете иметь контейнер - конечно, не стандартный контейнер - который содержит константы без каких-либо вид косвенности.
ИЗМЕНИТЬ. Если вы хотите, чтобы в основном оставить контейнер неизменным, но все же можно изменить его в определенных местах в коде, а затем использовать const ref в большинстве мест и то предоставление кода, который должен иметь возможность изменить прямой доступ к контейнеру, или не-const ref, сделает это возможным.
Итак, используйте const vector<int>&
в большинстве мест, а затем либо vector<int>&
, где вам нужно изменить контейнер, либо предоставить часть кода для прямого доступа к контейнеру. Таким образом, это в основном неизменное, но вы можете изменить его, когда захотите.
С другой стороны, если вы хотите иметь возможность в значительной степени всегда изменять то, что в контейнере, но не изменять определенные элементы, то я бы предложил разместить класс оболочки вокруг контейнера. В случае vector
, оберните его и заставьте оператор индекса возвращать константу ref вместо not-const ref - либо это, либо копию. Итак, предположив, что вы создали templatized версию, ваш оператор индекса будет выглядеть примерно так:
const T& operator[](size_t i) const
{
return _container[i];
}
Таким образом, вы можете обновить сам контейнер, но вы не можете изменить его отдельные элементы. И пока вы объявляете все функции inline, это не должно быть большим успехом (если вообще есть), чтобы иметь обертку.
Ответ 3
Вы не можете создать вектор const ints, и это было бы бесполезно, даже если бы вы могли. Если я удаляю второй int, тогда все оттуда сдвигается вниз на один - читается: modified - делает невозможным гарантировать, что v [5] имеет одно и то же значение в двух разных случаях.
Добавьте к этому, const не может быть назначен после того, как он был объявлен, за исключением того, что он отбрасывает константу. И если вы хотите сделать это, почему вы используете const в первую очередь?
Ответ 4
Вам нужно будет написать свой собственный класс. Вы можете использовать std::vector как свою внутреннюю реализацию. Затем просто реализуйте интерфейс const и те немногие неконстантные функции, которые вам нужны.
Ответ 5
Хотя это не соответствует всем вашим требованиям (возможность сортировки), попробуйте постоянный вектор:
int values[] = {1, 3, 5, 2, 4, 6};
const std::vector<int> IDs(values, values + sizeof(values));
Хотя вы можете использовать std::list
. Со списком значения не нужно изменять, только ссылки на них. Сортировка выполняется путем изменения порядка ссылок.
Вам, возможно, придется расходовать силы мозга и писать свои собственные.: - (
Ответ 6
У меня были бы все мои объекты const в стандартном массиве.
Затем используйте вектор указателей в массив.
Небольшой класс полезности, чтобы помочь вам не удалять ссылки на объекты и сено-престо.
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
class XPointer
{
public:
XPointer(int const& data)
: m_data(&data)
{}
operator int const&() const
{
return *m_data;
}
private:
int const* m_data;
};
int const data[] = { 15, 17, 22, 100, 3, 4};
std::vector<XPointer> sorted(data,data+6);
int main()
{
std::sort(sorted.begin(), sorted.end());
std::copy(sorted.begin(), sorted.end(), std::ostream_iterator<int>(std::cout, ", "));
int x = sorted[1];
}
Ответ 7
Я с Ноем: оберните вектор классом, который предоставляет только то, что вы хотите разрешить.
Если вам не нужно динамически добавлять объекты в вектор, рассмотрите std::tr1::array
.
Ответ 8
Верно, что Assignable является одним из стандартных требований к типу векторного элемента, а const int
не присваивается. Однако я ожидаю, что в продуманной реализации компиляция завершится неудачей, только если код явно использует назначение. Для std::vector
, например, insert
и erase
.
В действительности во многих реализациях компиляция не выполняется, даже если вы не используете эти методы. Например, Comeau не может скомпилировать plain std::vector<const int> a;
, потому что соответствующая специализация std::allocator
не скомпилируется. Он не сообщает о ближайших проблемах с самим std::vector
.
Я считаю, что это действительная проблема. Предполагается, что реализованная библиотекой реализация std::allocator
завершится неудачей, если параметр типа является const-квалифицированным. (Интересно, можно ли создать пользовательскую реализацию std::allocator
, чтобы заставить все это скомпилировать.) (Также было бы интересно узнать, как VS удается скомпилировать его). Снова, с Comeau std::vector<const int>
не удается скомпилировать по тем же причинам std::allocator<const int>
не скомпилируется, и, согласно спецификации std::allocator
, он не должен компилироваться.
Конечно, в любом случае любая реализация имеет право не скомпилировать std::vector<const int>
, так как она может выйти из строя по спецификации языка.
Ответ 9
Если в этом случае вам важна константа, я думаю, вы, вероятно, захотите работать с неизменяемыми типами. Концептуально вы будете иметь фиксированный размер, const-массив из const int
s. Каждый раз, когда вам нужно его менять (например, для добавления или удаления элементов или для сортировки), вам нужно будет сделать копию массива с выполненной операцией и использовать это вместо этого.
Хотя это очень естественно на функциональном языке, на С++ оно не кажется вполне "правильным". например, получение эффективных реализаций может быть сложным, но вы не говорите, каковы ваши требования к производительности.
Если вы рассматриваете этот маршрут как заслуживающий внимания с точки зрения производительности/пользовательского кода или нет, я считаю, что это правильный подход.
После этого, конечно, удержание значений указателем-не указателем-указателем/умным указателем является лучшим (но имеет свои собственные накладные расходы, конечно).
Ответ 10
Я немного подумывал об этой проблеме, и кажется, что ваше требование отключено.
Вы не хотите добавлять неизменяемые значения в свой вектор:
std::vector<const int> vec = /**/;
std::vector<const int>::const_iterator first = vec.begin();
std::sort(vec.begin(), vec.end());
assert(*vec.begin() == *first); // false, even though `const int`
То, что вы действительно хотите, это ваш вектор для хранения постоянного набора значений в модифицируемом порядке, который не может быть выражен синтаксисом std::vector<const int>
, даже если он сработал.
Я боюсь, что это чрезвычайно заданная задача, для которой потребуется выделенный класс.
Ответ 11
Используя только неспециализированный vector
, это невозможно. Сортировка выполняется с помощью назначения. Итак, тот же код, который делает это возможным:
sort(v.begin(), v.end());
... также делает это возможным:
v[1] = 123;
Ответ 12
Вы можете получить класс const_vector из std::vector, который перегружает любой метод, возвращающий ссылку, и вместо этого возвращает значение ссылки const. Чтобы сделать свой вид, опустите вниз до std::vector.
Ответ 13
std::vector
постоянного объекта, вероятно, не скомпилируется из-за требования Assignable
, поскольку постоянный объект не может быть назначен. То же самое верно и для Move Assignment. Это также проблема, с которой я часто сталкиваюсь при работе с векторной картой, например boost flat_map
или Loki AssocVector
. Поскольку он имеет внутреннюю реализацию std::vector<std::pair<const Key,Value> >
.
Таким образом, почти невозможно выполнить требование константы map, которое может быть легко реализовано для любой карты на основе node.
Однако можно посмотреть, означает ли std::vector<const T>
, что вектор должен хранить типизированный объект const T
, или просто нужно возвращать не изменяемый интерфейс при доступе.
В этом случае возможна реализация std::vector<const T>
, которая следует за назначением Assignable/Move Assignable, поскольку она хранит объект типа T
, а не const T
. Стандартные типы typedefs и allocator мало нуждаются в поддержке стандартных требований. Однако для поддержки таких vector_map
или flat_map
, возможно, потребуется значительное изменение в интерфейсе std::pair
, поскольку оно предоставляет непосредственные и непосредственные переменные-члены.
Ответ 14
Ошибка компиляции, потому что push_back()
(например) в основном
underlying_array[size()] = passed_value;
где оба операнда T&
. Если T
- const X
, который не может работать.
Составляющие элементы кажутся правильными в принципе, но на практике это неестественно, и спецификации не говорят, что они должны поддерживаться, поэтому их нет. По крайней мере, не в stdlib (потому что тогда это будет в векторе).