Оператор перегрузки [] для шаблонного класса полиномов

#c #operator-overloading #square-bracket

#c #оператор-перегрузка #квадратная скобка

Вопрос:

Я пишу шаблонный Polynom<T> класс, где T числовой тип его коэффициентов.

Коэффициенты полинома хранятся в std::vector<T> coefficients , где coefficients[i] соответствует x^i в реальном полиноме. (таким образом, степени x находятся в порядке возрастания).

Гарантируется, что coefficients вектор всегда содержит хотя бы один элемент. — для нулевого полинома это T() .

Я хочу перегрузить operator[] , чтобы выполнить следующее:

  1. Индекс, передаваемый оператору[], соответствует степени X, коэффициент которой мы хотим изменить / прочитать.
  2. Если пользователь хочет просто прочитать коэффициент, он должен выдавать значения для отрицательных индексов, возвращать coefficients.at(i) для индексов в пределах сохраненного диапазона — и разумно возвращать 0 для всех других индексов, а не выбрасывать.
  3. Если пользователь хочет изменить коэффициент, он должен выдавать значения для отрицательных индексов, но позволяет пользователю свободно изменять все остальные индексы, даже если указанный индекс больше или равен coefficients.size() . Итак, мы хотим каким-то образом изменить размер вектора.

Основная проблема, с которой я столкнулся, заключается в следующем:

1.

Как мне отличить регистр чтения от регистра записи? Один человек оставил меня без объяснения, но сказал, что пишет две версии:

 const Tamp; operator[] (int index) const;
Tamp; operator[] (int index);
  

было недостаточным. Однако я подумал, что компилятор предпочел бы версию const в случае чтения, не так ли?

2.

Я хочу убедиться, что в coefficients векторе никогда не сохраняются конечные нули. Итак, я каким-то образом должен знать заранее, «до» того, как я верну изменяемый Tamp; мой коэффициент, какое значение пользователь хочет присвоить. И я знаю, что operator[] не получает второго аргумента.

Очевидно, что если это значение не равно нулю (не T ()), то я должен изменить размер своего вектора и установить соответствующий коэффициент для переданного значения.

Но я не могу сделать это заранее (перед возвратом Tamp; from operator[] ), потому что если присваиваемое значение равно T (), то, при условии, что я заранее изменяю размер своего вектора коэффициентов, в конечном итоге в нем будет много конечных «нулей».

Конечно, я могу проверить наличие конечных нулей в любой другой функции класса и удалить их в этом случае. Мне кажется очень странным решение, и я хочу, чтобы каждая функция начинала работать в предположении, что в конце вектора нет нулей, если его размер> 1.

Не могли бы вы, пожалуйста, посоветовать мне как можно более конкретное решение этой проблемы? Я что-то слышал о написании внутреннего класса, неявно преобразуемого в Tamp; с помощью overloaded operator= , но мне не хватает подробностей.

Заранее большое вам спасибо!

Ответ №1:

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

 template<typename T>
class MyRef{
private:
   int index;
   Polynom<T>*p;
public:
    MyRef(int index, Polynom<T>*p) : index(index), p(p) { }

    MyRef<T>amp; operator=(T constamp;t); //and define these appropriately
    T operator T() const;         
};
  

и определить:

     MyRef<T> operator[](int index){
        return MyRef<T>(index, this);
    }
  

Таким образом, когда вы присваиваете значение «ссылке», оно должно иметь доступ ко всем необходимым данным в полиноме и выполнять соответствующие действия.

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

  • вы можете читать из любого int index без проблем; элементы, в которые ранее не были записаны, должны считываться как 0 ;
  • когда вы выполняете запись в элемент после конца текущего выделенного массива, он перераспределяется, а вновь выделенные элементы инициализируются 0 .
 #include <cstdlib>
#include <iostream>
using namespace std;

template<typename T>
class my_array{
private:
    T* _data;
    int _size;

    class my_ref{

        private:
            int index;
            T*amp; obj;
            intamp;size;
        public:
            my_ref(T*amp; obj, intamp;size, int index)
                : index(index), obj(obj), size(size){}

            my_refamp; operator=(T constamp; t){

                if (index>=size){    
                    obj = (T*)realloc(obj, sizeof(T)*(index 1) );
                    while (size<=index)
                        obj[size  ]=0;
                }
                obj[index] = t;

                return *this;
            }

            //edit:this one should allow writing, say, v[1]=v[2]=v[3]=4;
            my_refamp; operator=(const my_refamp;r){              
                operator=( (T) r);
                return *this;
            }

            operator T() const{
                return (index>=size)?0:obj[index];
            }

    };

public:
    my_array() : _data(NULL), _size(0) {}

    my_ref operator[](int index){
        return my_ref(_data,_size,index);
    }

    int size() const{ return _size; }

};

int main(){

    my_array<int> v;

    v[0] = 42;
    v[1] = 51;
    v[5] = 5; v[5]=6;
    v[30] = 18;

    v[2] = v[1] v[5];
    v[4] = v[8] v[1048576] v[5] 1000;

    cout << "allocated elements: " <<  v.size() << endl;
    for (int i=0;i<31;i  )
        cout << v[i] << " " << endl;

    return 0;
}
  

Это очень простой пример и не очень эффективный в его текущей форме, но он должен доказать суть.

В конечном итоге вы можете захотеть перегрузить, operatoramp; чтобы такие вещи, как *(amp;v[0] 5) = 42; , работали должным образом. Для этого примера у вас могло бы быть, что operatoramp; выдает my_pointer , которое определяет operator , как выполнить арифметику в своем index поле и вернуть новое my_pointer . Наконец, вы можете перегрузить operator*() , чтобы вернуться к my_ref .

Комментарии:

1. Спасибо! Не могли бы вы, пожалуйста, также уточнить следующее: 1. Должен ли я сделать этот класс внутренним и закрытым? 2. Могу ли я вернуть ссылку на экземпляр частного класса?

2. Я обновил свой пример, чтобы использовать частный вложенный класс. Это все еще работает (см.: ideone.com/dRr4D ) но я не слишком часто использовал вложенные классы в прошлом, поэтому я не знаю, каковы наилучшие практики.

3. Это не будет охватывать такие варианты использования, как int amp;r = v[1]; r = 3; .

4. Правда, есть ограничения. Но вы всегда можете (предполагая, что my_ref они общедоступны) использовать my_array<int>::my_ref r = v[1]; r= 3; , если это необходимо.

Ответ №2:

Решением этого является прокси-класс (далее следует непроверенный код):

 template<typename T> class Polynom
{
public:
   class IndexProxy;
   friend class IndexProxy;
   IndexProxy operator[](int);
   T operator[](int) const;
   // ...
private:
   std::vector<T> coefficients;
};

template<typename T> class Polynom<T>::IndexProxy
{
public:
  friend class Polynom<T>;
  // contrary to convention this assignment does not return an lvalue,
  // in order to be able to avoid extending the vector on assignment of 0.0
  T operator=(T constamp; t)
  {
    if (theIndex >= thePolynom.coefficients.size())
      thePolynom.coefficients.resize(theIndex 1);
    thePolynom.coefficients[theIndex] = t;
    // the assignment might have made the polynom shorter
    // by assigning 0 to the top-most coefficient
    while (thePolynom.coefficients.back() == T())
      thePolynom.coefficients.pop_back();
    return t;
  }
  operator T() const
  {
    if (theIndex >= thePolynom.coefficients.size())
      return 0;
    return thePolynom.coefficients[theIndex];
  }
private:
  IndexProxy(Polynom<T>amp; p, int i): thePolynom(p), theIndex(i) {}
  Polynom<T>amp; thePolynom;
  int theIndex;
}

template<typename T>
  Polynom<T>::IndexProxy operator[](int i)
  {
    if (i < 0) throw whatever;
    return IndexProxy(*this, i);
  }

template<typename T>
  T operator[](int i)
{
  if (i<0) throw whatever;
  if (i >= coefficients.size()) return T();
  return coefficients[i];
}
  

Очевидно, что приведенный выше код не оптимизирован (особенно в операторе присваивания явно есть место для оптимизации).

Комментарии:

1. 1. Могу я попросить вас пояснить, почему вы решили не заставлять operator= возвращать значение lvalue? Я пытаюсь подумать о возможных проблемах, когда он возвращает amp; , но я не могу придумать ни одной прямо сейчас.

2. Возврат T amp; подразумевал бы наличие объекта для его привязки, который мог бы не существовать в случае присвоения 0. Как я только сейчас заметил, я мог бы также вернуть ссылку на сам IndexProxy. Но единственный дополнительный код, который позволяет, будет иметь форму, которую вы все равно не хотели бы писать; код, который вы могли бы захотеть написать (привязка результата выражения присваивания к ссылке на T), все равно не будет работать.

3. Спасибо за ответ. Что касается последней части, вы правы, это не сработало, я добавил еще одну перегрузку для operator= , чтобы заставить этот случай работать.

Ответ №3:

Вы не можете отличить чтение от записи с перегрузками оператора. Лучшее, что вы можете сделать, это провести различие между использованием в const настройках и без const настроек, что и делает ваш фрагмент кода. Итак:

 Polynomial amp;poly = ...;

poly[i] = 10;  // Calls non-const version
int x = poly[i];  // Calls non-const version

const Polynomial amp;poly = ...;

poly[i] = 10;   // Compiler error!
int x = poly[i]  // Calls const version
  

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

Ответ №4:

Я вижу два решения вашей проблемы:

  1. Вместо того, чтобы хранить коэффициенты в a, std::vector<T> храните их в std::map<unsigned int, T> . Таким образом, вы всегда будете хранить только ненулевые коэффициенты. Вы могли бы создать свой собственный контейнер на std::map основе, который потреблял бы хранящиеся в нем нули. Таким образом, вы также экономите некоторое пространство памяти для полиномов вида x ^ n с большим n.

  2. Добавьте внутренний класс, который будет хранить индекс (мощность) и значение коэффициента. Вы бы вернули ссылку на экземпляр этого внутреннего класса из operator[] . Внутренний класс был бы перезаписан operator= . В переопределенном operator= вы бы взяли индекс (мощность) и коэффициент, хранящиеся во внутреннем экземпляре класса, и сбросили их туда, std::vector где вы храните свои коэффициенты.

Комментарии:

1. std::map также хранит нулевые элементы. Более того, если вы запрашиваете у него элемент, которого он еще не содержит, он создает is со значением ноль. Например: std::map<int,int> m; int k=m[1]; k=m[3]; std::cout << m.size(); выводит 2, потому что теперь в map есть две пары ключ-значение: (1,0) и (3,0).

Ответ №5:

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

Компилятор выбирает между const и не const версией, глядя на тип Polynom , а не проверяя, какого рода операция выполняется над возвращаемым значением.