#c #operator-overloading #square-bracket
#c #оператор-перегрузка #квадратная скобка
Вопрос:
Я пишу шаблонный Polynom<T>
класс, где T
числовой тип его коэффициентов.
Коэффициенты полинома хранятся в std::vector<T> coefficients
, где coefficients[i]
соответствует x^i
в реальном полиноме. (таким образом, степени x находятся в порядке возрастания).
Гарантируется, что coefficients
вектор всегда содержит хотя бы один элемент. — для нулевого полинома это T()
.
Я хочу перегрузить operator[]
, чтобы выполнить следующее:
- Индекс, передаваемый оператору[], соответствует степени X, коэффициент которой мы хотим изменить / прочитать.
- Если пользователь хочет просто прочитать коэффициент, он должен выдавать значения для отрицательных индексов, возвращать
coefficients.at(i)
для индексов в пределах сохраненного диапазона — и разумно возвращать 0 для всех других индексов, а не выбрасывать. - Если пользователь хочет изменить коэффициент, он должен выдавать значения для отрицательных индексов, но позволяет пользователю свободно изменять все остальные индексы, даже если указанный индекс больше или равен
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:
Я вижу два решения вашей проблемы:
-
Вместо того, чтобы хранить коэффициенты в a,
std::vector<T>
храните их вstd::map<unsigned int, T>
. Таким образом, вы всегда будете хранить только ненулевые коэффициенты. Вы могли бы создать свой собственный контейнер наstd::map
основе, который потреблял бы хранящиеся в нем нули. Таким образом, вы также экономите некоторое пространство памяти для полиномов вида x ^ n с большим n. -
Добавьте внутренний класс, который будет хранить индекс (мощность) и значение коэффициента. Вы бы вернули ссылку на экземпляр этого внутреннего класса из
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
, а не проверяя, какого рода операция выполняется над возвращаемым значением.