Как предотвратить присвоение значения с унаследованным оператором []? - PullRequest
0 голосов
/ 26 апреля 2018

У меня есть пользовательская структура с именем SortedArrayList<T>, которая сортирует свои элементы в соответствии с компаратором, и я хотел бы предотвратить назначение с использованием operator[].

Пример:

ArrayList.h

template <typename T> class ArrayList : public List<T> {
    virtual T& operator[](const int& index) override; //override List<T>
    virtual const T operator[](const int& index) const override; //override List<T>
}

SortedLinkedList.h со следующими операторами

template <typename T> class SortedArrayList : public ArrayList<T> {
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   T& operator[](const int& index) override; //get reference (LHS)
   const T operator[](const int& index) const override; //get copy (RHS)
}

test.h

ArrayList<int>* regular = new ArrayList<int>();
ArrayList<int>* sorted = new SortedArrayList<int>(cmpfn);

(*regular)[0] == 5; //allow
(*regular)[0] = 5;  //allow
(*sorted)[0] == 7; //allow
(*sorted)[0] = 7; //except

Возможна ли эта операция?

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

Ответы [ 4 ]

0 голосов
/ 26 апреля 2018

Ты не должен этого делать. Это указывает на неправильный дизайн. См. FAQ C ++ по наследованию . Ваш подкласс не соответствует требованию "is-a" для публичного наследования, если его нельзя использовать во всех отношениях как базовый класс ( LSP ).

Если вы хотите иметь один тип контейнера, который позволяет заменять элементы, а другой - нет, то определите базовый класс, который просто разрешает доступ к элементу const (нет необходимости делать его виртуальным). Затем перейдите оттуда к MutableList и ImmutableList, и пусть SortedArrayList будет производным от неизменного списка.

0 голосов
/ 26 апреля 2018
  1. Почему вы вообще передаете индекс в качестве ссылки? Абсолютно не нужно ...
  2. Я лично рекомендую использовать целочисленные типы без знака для индексов массива (что в любом случае будет означать отрицательный индекс ???).
  3. const для типа, возвращаемого значением, является (почти) бессмысленным - он все равно будет скопирован в другую переменную (которая тогда будет изменяемой), но вы предотвратите семантику перемещения ...

Итак:

T& operator[](unsigned int index); //get reference (LHS)
T operator[](unsigned int index) const; //get copy (RHS)

(Только некоторые предложения по улучшению ...)

Теперь к актуальному вопросу: запретить модификацию довольно просто:

//T& operator[](unsigned int index); //get reference (LHS)
T const& operator[](unsigned int index) const; //get copy (RHS)

Всего один оператор индекса, всегда возвращающий константную ссылку ... Если пользователь может жить со ссылкой, хорошо, иначе он / она все равно скопирует значение ...

Изменить в адаптации к измененному вопросу:

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

В данной ситуации я бы рассмотрел редизайн (если это возможно):

class ArrayListBase
{
public:
    T const& operator[](unsigned int index) const;
    // either copy or const reference, whichever appears more appropriate to you...
};

class ArrayList : public ArrayListBase
{
public:
    using ArrayListBase::operator[];
    T& operator[](unsigned int index);
}


class SortedArrayList : public ArrayListBase
{
public:
    // well, simply does not add an overload...
}

Функции вставки могут быть чисто виртуальными в базовом классе (где подходит общий интерфейс) или доступны только в производных классах. Решай ты ...

0 голосов
/ 26 апреля 2018

Предпочитают агрегирование перед наследованием:

template <typename T> class SortedArrayList {
   ArrayList<T> m_the_list;
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   const T& operator[](const int& index) const {return m_the_list[index];}; // always get const reference

   // Can act as a *const* ArrayList<T>, but not as a mutable ArrayList<T>, as that would violate Liskov's substitution principle.
   operator const ArrayList<T>&() const {return m_the_list;}
}

Как Стивен Ньюэлл правильно указывает , когда вы используете наследование, вы гарантируете, что ваш класс SortedArrayList может действовать как ArrayList в каждом возможном сценарии. Это явно не тот случай в вашем примере.

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

0 голосов
/ 26 апреля 2018

Мне кажется, что наилучшей практикой здесь будет реализация метода at(const int& index) вместо перегрузки []. В любом случае, это было бы более понятно пользователю интерфейса.

Существует похожая функция в std::map и других std структурах данных. Например: http://www.cplusplus.com/reference/map/map/at/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...