Разрешить изменение только ненулевых элементов разреженной матрицы
Автор вопроса: ikottmanОтветов: 4
Я реализую трехдиагональную матрицу, и я должен быть максимально эффективным. Очевидно, я буду хранить только те элементы, которые содержат данные. Я перегрузил operator(), чтобы он действовал как индексатор в матрице, но я хочу, чтобы этот оператор возвращал ссылку, чтобы пользователь мог изменить матрицу. Однако я не могу просто return 0; для нетрехдиагональных элементов, поскольку ноль не является ссылкой. Как разрешить пользователю изменять данные в трехдиагональном, но когда operator() используется для проверки нетрехдиагонального элемента, возвращать только 0 вместо ссылки на 0?
ниже приведено определение связанного класса
template <class T>
class tridiagonal
{
public:
tridiagonal();
~tridiagonal();
T& operator()(int i, int j);
const T& operator()(int i, int j) const;
private:
//holds data of just the diagonals
T * m_upper;
T * m_main;
T * m_lower;
};
У вас проблема в неподходящем интерфейсе. Если ваше определение матрицы - это двумерный массив чисел, в котором каждый элемент матрицы может быть задан индивидуально, то разреженная трехмерная матрица, как это ни парадоксально, не является матрицей (точно так же, как квадрат не является модифицируемый прямоугольник - классический пример неподходящего наследования, не подчиняющегося принципу подстановки Лискова).
Короче говоря, вам лучше изменить свой интерфейс, чтобы он соответствовал разреженным трехдиагональным матрицам, чем пытаться взломать его, чтобы он работал с интерфейсом, который у вас есть. Тем не менее, если вы должны сделать это таким образом, вам, вероятно, лучше сделать две вещи:
Изменение вашего метода доступа const для возврата T вместо const T& (я предполагаю, что здесь мы имеем дело только с матрицами чисел). Затем вы можете просто вернуть 0 для элементов вне диагонали.
Изменение вашего не-const метода доступа, чтобы он возвращал ссылку на фиктивный элемент для местоположений вне диагонали, и скрещивание пальцев :) В качестве альтернативы вы можете изменить спецификацию на throw в таких случаях, но это может быть немного недружелюбно.
Еще одна альтернатива (кроме правильной переработки интерфейса) может состоять в том, чтобы возвращать прокси-объекты вместо Ts. Затем прокси для фиктивных элементов будет throw, когда вы попытаетесь установить значение, используя его.
Ответ 2 · рейтинг 1 · David Norman
Один трюк, который вы можете использовать, состоит в том, чтобы неконстантный метод operator() (int, int) возвращал небольшой вспомогательный объект. Помощник используется, чтобы различать назначение в матрицу и просто извлечение значения. Это позволяет иметь различное поведение для двух операций. В частности, вы можете бросать, если кто-то пытается присвоить значение, которое должно быть равно нулю.
Этот код, по крайней мере, компилируется для меня в VC10, но, очевидно, не линкуется.
template <class T>
class tridiagonal
{
public:
// Helper class that let's us tell when the user is
// assigning into the matrix and when they are just
// getting values.
class helper
{
tridiagonal<T> &m_parent;
int m_i, m_j;
public:
helper(tridiagonal<T> &parent, int i, int j)
: m_parent(parent), m_i(i), m_j(j)
{}
// Converts the helper class to the underlying
// matrix value. This doesn't allow assignment.
operator const T & () const {
// Just call the const operator()
const tridiagonal<T> &constParent = m_parent;
return constParent(m_i, m_j);
}
// Assign a value into the matrix.
// This is only called for assignment.
const T & operator= (const T &newVal) {
// If we are pointing off the diagonal, throw
if (abs(m_i - m_j) > 1) {
throw std::exception("Tried to assign to a const matrix element");
}
return m_parent.assign(m_i, m_j, newVal);
}
};
tridiagonal();
~tridiagonal();
helper operator()(int i, int j)
{
return helper(*this, i,j);
}
const T& operator()(int i, int j) const;
private:
T& assign(int i, int j, const T &newVal);
//holds data of just the diagonals
T * m_upper;
T * m_main;
T * m_lower;
};
int main(int argc, const char * argv[])
{
tridiagonal<double> mat;
std::cout << mat(0,0) << std::endl;
const tridiagonal<double> & constMat = mat;
std::cout << mat(2,3) << std::endl;
// Compiles and works
mat(2,3) = 10.0;
// Compiles, but throws at runtime
mat(1, 5) = 20.0;
// Doesn't compile
// constMat(3,3) = 12.0;
return 0;
}
Прошло некоторое время с тех пор, как я сделал это, поэтому вы можете обнаружить, что вам нужно добавить немного больше во вспомогательный класс, в зависимости от того, как вы используете матрицу.
На самом деле проработка этого - хорошее упражнение на С++. :)
Ответ 3 · jli
Возврат по ссылке требует, чтобы вы возвращали действительный объект указанного типа. Самый простой способ добиться того, что вы хотите, - сохранить статический объект T, представляющий 0, и вместо этого вернуть его.
В качестве альтернативы вы можете вернуть указатель.
Ответ 4 · André Caron
Просто добавьте дополнительный элемент, представляющий некоторое фиктивное значение, и убедитесь, что он всегда читается как 0.
template<typename T>
class tridiagonal
{
// usual stuff...
T& operator() (int j, int j)
{
// if not explicitly stored, reset to default before returning.
return stored(i,j)? fetch(i,j) : (m_dummy=T());
}
private:
// dummy element used to "reference" elements outside the 3 diagonals.
T m_dummy;
// check if (i,j) is on 3 diagonals.
bool stored (int i, int j) const;
// access element on 3 diagonals. precondition: stored(i,j)==true.
T& fetch (int i, int j);
//holds data of just the diagonals
T * m_upper;
T * m_main;
T * m_lower;
};
Обратите внимание, что с технической точки зрения кто-то может вас обмануть: