2012-04-09 3 views
2

Я реализую трехдиагональную матрицу, и я должен быть максимально эффективным. Очевидно, что я буду содержать только элементы, содержащие данные. Я перегрузил 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; 
}; 

ответ

0

Проблема, которая возникает у вас здесь, является неподходящим интерфейсом. Если ваше определение матрицы является двумерным массивом чисел, так что каждый элемент матрицы может быть индивидуально установлен, то разреженная, тридиагональная матрица парадоксально не матрица (так же как квадрат не является модифицируемым rectangle - классический пример ненадлежащего наследования, который не подчиняется Принципу замещения Лискова).

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

  • модифицируя const аксессор вернуть T вместо const T& (я предполагаю, что мы имеем дело только с матрицами номера здесь). Затем вы можете просто вернуть 0 для элементов с диагонали.
  • Модификация вашего аксессуара non const для возврата ссылки на фиктивный элемент для местоположений по диагонали и пересечения пальцев :) В качестве альтернативы вы можете изменить спецификацию на throw в таких случаях, но это может быть немного недружелюбным.

Другой альтернативой (недолго переработанный интерфейс) может быть возвращение объектов-прокси вместо T. Прокси для фиктивных элементов затем будет throw, когда вы попытаетесь установить значение с помощью этого.

+0

Я выбрал бросить, когда кто-то пытается изменить недиагональный элемент. Это действительно беспорядочно, но назначение требует, чтобы интерфейс был таким, какой он есть. Спасибо за помощь. –

0

Возвращение по ссылке требует, чтобы вы вернуть действительный объект указанного типа. Самый простой способ выполнить то, что вы хотите, - сохранить статический объект T, который представляет 0, и вместо этого вернуть его.

В качестве альтернативы вы можете вернуть указатель.

0

Просто добавьте дополнительный элемент, представляющий некоторую фиктивную ценность и убедитесь, что он всегда читает как 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; 
}; 

Обратите внимание, что с технической точки зрения, кто-то может обмануть вас, как например:

tridiagonal<int> m(4,4); 
T * dummy = &m(3,0); // *dummy == 0. 
*dummy = 1;   // *dummy == 1. 
std::cout << *dummy; // prints 1. 

Но это не обязательно проблема.

1

Один трюк, который вы можете использовать, заключается в том, чтобы метод non-const 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; 
} 

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

Фактически, работа над этим является хорошим упражнением на C++. :)

Смежные вопросы