仅允许修改稀疏矩阵的非零元素

时间:2012-04-09 23:46:21

标签: c++ operator-overloading sparse-matrix

我正在实现一个三对角矩阵,我必须尽可能高效。显然我只会持有包含数据的元素。我重载了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;
};

4 个答案:

答案 0 :(得分:1)

您可以使用的一个技巧是让非const运算符()(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 ++练习。 :)

答案 1 :(得分:0)

通过引用返回要求您返回指定类型的有效对象。完成所需操作的最简单方法是保留一个表示0的静态T对象,然后返回它。

或者,您可以返回一个指针。

答案 2 :(得分: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.

但这不一定是个问题。

答案 3 :(得分:0)

您在这里遇到的问题是不恰当的界面。如果你对矩阵的定义是一个二维数组,这样矩阵的每个元素都可以单独设置,那么一个稀疏的三维矩阵自相矛盾地一个矩阵(正如一个正方形不是一个矩阵) 可修改的矩形 - 不遵守Liskov替换原则的不恰当继承的典型示例。

简而言之,您最好更改界面以适应稀疏的三对角矩阵,而不是试图破解它以使用您所拥有的界面。也就是说,如果你必须这样做,那么你最好做两件事:

  • 修改您的const访问者以返回T而不是const T&(我假设我们这里只处理数字矩阵)。然后,您可以返回对角线以外的元素0
  • 修改非const访问器以返回对角线以外位置的虚拟元素的引用,并交叉手指:)或者,在这种情况下,您可以将规范更改为throw,但这可能有点不友好。

另一种替代方法(没有正确地重新设置接口)可能是返回代理对象而不是T。当您尝试使用它设置值时,虚拟元素的代理将throw