Разрешить изменение только ненулевых элементов разреженной матрицы

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

person ikottman    schedule 09.04.2012    source источник


Ответы (4)


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

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

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

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

person Stuart Golodetz    schedule 10.04.2012
comment
Я решил бросать, когда кто-то пытается изменить недиагональный элемент. Это действительно беспорядочно, но назначение требует, чтобы интерфейс был таким, какой он есть. Спасибо за помощь. - person ikottman; 11.04.2012

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

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

На самом деле проработка этого — хорошее упражнение на С++. :)

person David Norman    schedule 10.04.2012

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

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

person jli    schedule 09.04.2012

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

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

person André Caron    schedule 09.04.2012