Могу ли я использовать const в векторах, чтобы разрешить добавлять элементы, но не изменять уже добавленные? - PullRequest
60 голосов
/ 03 мая 2010

Мои комментарии к этому ответу заставили меня задуматься о проблемах константности и сортировки. Я немного поиграл и свел свои проблемы к тому, что этот код:

#include <vector>

int main() {
    std::vector <const int> v;  
}

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

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

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

Ответы [ 14 ]

22 голосов
/ 03 мая 2010

Ну, в C ++ 0x вы можете ...

В C ++ 03 есть параграф 23.1 [lib.containers.requirements] / 3, в котором говорится

Тип объектов, хранящихся в этих компонентах, должен соответствовать требованиям CopyConstructible типов (20.1.3) и дополнительным требованиям Assignable типов.

Это то, что в настоящее время мешает вам использовать const int в качестве аргумента типа для std::vector.

Однако в C ++ 0x этот параграф отсутствует, вместо этого T должен быть Destructible, и для каждого выражения указываются дополнительные требования к T, например, v = u на std::vector действителен, только если T равен MoveConstructible и MoveAssignable.

Если я правильно интерпретирую эти требования, у меня будет возможность создать экземпляр std::vector<const int>, вам просто не хватит некоторых его функций (я полагаю, это то, что вы хотели). Вы можете заполнить его, передав пару итераторов конструктору. Я думаю, что emplace_back() также должен работать, хотя я не нашел явных требований к T для него.

Вы все равно не сможете отсортировать вектор на месте.

17 голосов
/ 03 мая 2010

Типы, которые вы помещаете в стандартный контейнер, должны быть копируемыми и назначаемыми. Причина, по которой auto_ptr вызывает столько проблем, заключается именно в том, что она не следует обычной семантике копирования и назначения. Естественно, все, что const не будет назначено. Таким образом, вы не можете вставить const ничего в стандартный контейнер. И если элемент не const, то вы сможете его изменить.

Самое близкое решение, которое я считаю возможным, - это использовать какое-то косвенное направление. Таким образом, у вас может быть указатель на const, или у вас может быть объект, который содержит желаемое значение, но значение не может быть изменено внутри объекта (как вы получили бы с Integer в Java).

Наличие неизменного элемента по определенному индексу противоречит принципам работы стандартных контейнеров. Возможно, вы сможете создать свои собственные, которые работают таким образом, но стандартные не делают. И ни один из них, основанный на массивах, не будет работать независимо от того, если вы не сможете уместить их инициализацию в синтаксис инициализации {a, b, c}, поскольку после создания массива const вы не сможете его изменить. Таким образом, класс vector вряд ли будет работать с константными элементами независимо от того, что вы делаете.

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

РЕДАКТИРОВАТЬ : Если вы хотите оставить контейнер в основном без изменений, но при этом иметь возможность изменять его в определенных местах кода, тогда в большинстве мест используйте const ref, а затем предоставление кода, который должен быть в состоянии изменить прямой доступ к контейнеру или неконстантная ссылка, сделает это возможным.

Итак, используйте const vector<int>& в большинстве мест, а затем либо vector<int>&, где вам нужно изменить контейнер, либо предоставьте этой части кода прямой доступ к контейнеру. Таким образом, он в основном неизменен, но вы можете изменить его, когда захотите.

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

const T& operator[](size_t i) const
{
    return _container[i];
}

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

10 голосов
/ 03 мая 2010

Вы не можете создать вектор константных целых, и это было бы довольно бесполезно, даже если бы вы могли. Если я удаляю второй int, то все с этого момента смещается вниз на один - читай: модифицированный - что делает невозможным гарантировать, что v [5] будет иметь одинаковое значение в двух разных случаях.

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

7 голосов
/ 03 мая 2010

Тебе нужно написать свой собственный класс. Конечно, вы можете использовать std :: vector в качестве внутренней реализации. Затем просто реализуйте интерфейс const и те несколько неконстантных функций, которые вам нужны.

4 голосов
/ 03 мая 2010

Хотя это не соответствует всем вашим требованиям (возможность сортировки), попробуйте постоянный вектор:

int values[] = {1, 3, 5, 2, 4, 6};
const std::vector<int> IDs(values, values + sizeof(values));

Хотя вы можете использовать std::list. Со списком значения не нужно менять, только ссылки на них. Сортировка осуществляется путем изменения порядка ссылок.

Возможно, вам придется потратить немного умственных способностей и написать свой собственный. : - (

2 голосов
/ 03 мая 2010

Все мои константные объекты были бы в стандартном массиве.
Затем используйте вектор указателей в массиве.
Небольшой служебный класс, чтобы помочь вам не ссылаться на объекты и сено.

#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];
}
1 голос
/ 03 мая 2010

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

Вы не хотите добавлять неизменные значения в ваш вектор:

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>, даже если он работал.

Я боюсь, что это крайне заданная задачадля этого потребуется специальный класс.

1 голос
/ 03 мая 2010

Если вам важна константность в этом случае, я думаю, что вы, вероятно, хотите работать с неизменяемыми типами до самого конца. Концептуально у вас будет фиксированный размер, const массив const int s. Каждый раз, когда вам нужно изменить его (например, добавить или удалить элементы или отсортировать), вам нужно будет сделать копию массива с выполненной операцией и использовать ее вместо этого. Хотя это очень естественно для функционального языка, в C ++ оно выглядит не совсем «правильным». Например, получить эффективные реализации сортировки может быть сложно, но вы не говорите, каковы ваши требования к производительности. Считаете ли вы, что этот маршрут стоит того с точки зрения производительности / пользовательского кода или нет, я считаю, что это правильный подход.

После этого удержание значений неконстантным / умным указателем, вероятно, является лучшим (но, конечно, имеет свои издержки).

1 голос
/ 03 мая 2010

Это правда, что Назначаемое является одним из стандартных требований для типа векторного элемента, а const int нельзя назначить. Тем не менее, я ожидаю, что в хорошо продуманной реализации компиляция должна завершиться неудачей, только если код явно полагается на присваивание. Например, для std::vector это будет insert и erase.

В действительности, во многих реализациях компиляция завершается неудачно, даже если вы не используете эти методы. Например, Comeau не может скомпилировать простой std::vector<const int> a;, потому что соответствующая специализация std::allocator не скомпилируется. Он не сообщает о непосредственных проблемах с самим std::vector.

Я считаю, что это серьезная проблема. Реализация библиотеки std::allocator, предоставляемая библиотекой, должна завершиться ошибкой, если параметр типа является квалифицированным. (Интересно, можно ли сделать пользовательскую реализацию std::allocator, чтобы заставить все это скомпилировать.) (Было бы также интересно узнать, как VS компилирует это) по тем же причинам std::allocator<const int> не скомпилируется и, согласно спецификации std::allocator, должен не скомпилироваться.

Конечно, в любом случае любая реализация имеет право на сбой при компиляции std::vector<const int>, поскольку допускается сбой по спецификации языка.

1 голос
/ 03 мая 2010

Я с Ноем: оберните вектор классом, который выставляет только то, что вы хотите разрешить.

Если вам не нужно динамически добавлять объекты в вектор, рассмотрите std::tr1::array.

...