【问题标题】:Allow modification of only non-zero elements of a sparse matrix只允许修改稀疏矩阵的非零元素
【发布时间】:2012-04-10 09:16:42
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    您可以使用的一个技巧是让非常量 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++ 练习。 :)

    【讨论】:

      【解决方案2】:

      您遇到的问题是界面不合适。如果您对矩阵的定义是一个二维数字数组,这样矩阵的每个元素都可以单独设置,那么一个稀疏的三对角矩阵是矛盾的不是矩阵(就像正方形不是可修改矩形 - 不符合 Liskov 替换原则的不适当继承的经典示例。

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

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

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

      【讨论】:

      • 当有人试图修改非对角元素时,我选择了抛出。确实是乱七八糟,但是赋值要求界面原样。感谢您的帮助。
      【解决方案3】:

      通过引用返回要求您返回指定类型的有效对象。完成您想要的最简单的方法是保留一个代表 0 的静态 T 对象,然后将其返回。

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

      【讨论】:

        【解决方案4】:

        只需添加一个代表某个虚拟值的额外成员,并确保它始终读取为 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.
        

        但这不一定是个问题。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多