Подтвердить что ты не робот

Упрощение перегрузки const?

Я много лет преподаю класс программирования на C++, и одна из самых сложных вещей, которые нужно объяснить учащимся, - это перегрузка. Обычно я использую пример векторного класса и его функцию operator[]:

template <typename T> class Vector {
public:
    T& operator[] (size_t index);
    const T& operator[] (size_t index) const;
};

Мне нечего беспокоиться о том, почему нужны две версии функции operator[], но, пытаясь объяснить, как объединить две реализации вместе, я часто теряю много времени с помощью языка arcana. Проблема в том, что единственный хороший, надежный способ, которым я знаю, как реализовать одну из этих функций в терминах другой, заключается в трюке const_cast/static_cast:

template <typename T> const T& Vector<T>::operator[] (size_t index) const {
     /* ... your implementation here ... */
}
template <typename T> T& Vector<T>::operator[] (size_t index) {
    return const_cast<T&>(static_cast<const Vector&>(*this)[index]);
}

Проблема с этой установкой заключается в том, что ее чрезвычайно сложно объяснить и вовсе не интуитивно очевидно. Когда вы объясняете это как "cast to const", тогда вызывайте версию const, а затем стягивайте константу ", это немного легче понять, но фактический синтаксис пугает. Объясняя, что такое const_cast, почему это уместно здесь и почему оно почти повсеместно неуместно в других местах, обычно занимает от пяти до десяти минут времени лекции, и понимание этого всего выражения часто требует большего усилия, чем разница между const T* и T* const. Я чувствую, что студенты должны знать о const-перегрузке и как это сделать без необходимости дублирования кода в двух функциях, но этот трюк кажется немного чрезмерным во вводном курсе программирования на С++.

Мой вопрос заключается в следующем: существует ли более простой способ реализации const -перегруженных функций в терминах друг друга? Или есть более простой способ объяснить этот существующий трюк студентам?

4b9b3361

Ответ 1

Как просто разбить его на более мелкие шаги?

const Vector<T>& const_this = *this;
const T& const_elem = const_this[index];
T& mutable_elem = const_cast<T&>(const_elem);
return mutable_elem;

Вы можете даже удалить static_cast таким образом, хотя вы можете оставить его, если считаете, что это будет более ясным.

Ответ 2

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

В вашей способности преподавать С++ я бы чувствовал еще более сильный подход к этому подходу.

Ответ 3

Это довольно странный вариант, но это можно сделать с помощью помощника статического шаблона, такого как

// template parameters can be const or non-const
template<class Ret, class C>
static Ret& get(C* p, size_t index) { /* common code here like p->array[index] */ }

Затем вы можете написать

const T& operator[](size_t index) const { return get<const T>(this, index); }
T& operator[](size_t index) { return get<T>(this, index); }

Этот трюк позволяет избежать приведения (!) и двойной реализации, но, опять же, это выглядит странно для меня:)

И небольшое замечание о вашем коде фрагмента, не будет const_cast достаточно, а не static_cast, или я что-то не хватает?

Ответ 4

Вы можете удалить один листинг с помощью частного метода:
Он добавляет метод, но делает литье менее сложным:

template <typename T>
class Vector
{
  public:
    T const& operator[](size_t i) const { return getValue(i);}
    T&       operator[](size_t i)       { return const_cast<T&>(getValue(i));}

  private:
    T const& getValue(size_t i) const   { return /* STUFF */;}
};

Ответ 5

По-моему, это просто глупо. Вы заставляете его реализовываться с точки зрения другого просто ради этого, а не потому, что полученный код легче поддерживать или понимать. Причина, по которой ваши ученики путаются, вероятно, потому, что они ДОЛЖНЫ быть.

Не каждый принцип должен быть применен к исключительной крайности. Иногда избыточность - это просто лучше.

Ответ 6

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

В качестве альтернативы вы можете использовать локальные переменные, чтобы разбить выражение на управляемые части (с комментариями для каждого).

const ClassType& cthis = *this; // look, no explicit cast needed here :)
const T& elem = cthis[index]; // delegate to const version
return const_cast<T&>(elem); // ok to strip off the const, since we added it in the first place

Ответ 7

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