2012-04-09 177 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

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

总之,你最好改变你的界面以适应稀疏的三对角矩阵,而不是试图破解它与你所拥有的界面一起工作。也就是说,如果你必须这样做,那么你可能会更好做两件事情:

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

另一个替代方案(缺少适当修改接口)可能是返回代理对象而不是T s。当您尝试并使用它设置值时,虚拟元素的代理将为throw

+0

我选择在有人尝试修改非对角线元素时抛出。这确实很麻烦,但是分配要求界面保持原样。谢谢您的帮助。 – 2012-04-11 00:06:20

0

通过引用返回要求您返回指定类型的有效对象。实现你想要的最简单的方法是保持一个代表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

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