0

我正在读一本关于安全 C++ 的书。在这里,作者提到了如何避免超出范围的数组读取。在这里,提到了我们如何避免多维数组的超出范围的数组读取。这里作者使用operator()如下链接所示的函数代替operator[]并给出了以下解释。

https://github.com/vladimir-kushnir/SafeCPlusPlus/blob/master/scpp_matrix.hpp

请注意,要访问多维数组,我们要么需要使用多个[]运算符,例如 matrix[i][j],要么需要使用单个()运算符,例如 matrix(i,j)。

[]如果我们让运算符返回一个T*指向第 i 行第零个元素的指针,则可以实现第一种方法。但是,这否定了我们对列索引越界的诊断,这违背了在运行时捕获错误的目的。当然,我们可以创建一些包含对行的智能引用的模板类,返回使用第一个运算符([i])的实例,然后在第二个运算符([j])中使用边界检查。

我的问题是作者所说的“创建一些包含对行的智能引用的模板类,返回使用第一个运算符([i])的实例,然后在第二个运算符([j])中使用边界检查”是什么意思。? 请求提供示例代码我们如何在 C++ 中实现上述逻辑?

感谢您的时间和帮助。

4

1 回答 1

4

基本思想如下所示:

#include <vector>
#include <iostream>

template <class T>
class matrix { 
    size_t cols;
    size_t rows;
    std::vector<T> data;

    class row_proxy {  // This class is the part the question really asked about
        size_t row;
        matrix &m;
    public:
        row_proxy(matrix &m, size_t row) : row(row), m(m) {}

        T &operator[](size_t col) {
            if (row >= m.rows || col >= m.cols) // Note: row & col are indices not array count
                throw std::logic_error("Bad index");
            return m.data[row * m.cols + col];
        }
    };

public:

    matrix(size_t cols, size_t rows) : rows(rows), cols(cols), data(rows*cols) {}

    row_proxy operator[](size_t row) { 
        return row_proxy(*this, row);
    }
};

int main() { 
    matrix<int> m(3, 3);

    for (int i=0; i<3; i++)   // fill the matrix with identifiable numbers
        for (int j=0; j<3; j++)
            m[i][j] = i * 100 + j;

    for (int i=0; i<3; i++) { // show the content
        for (int j=0; j<3; j++)
            std::cout << m[i][j] << "\t";
        std::cout << "\n";
    }

    try {                     // test the bounds checking.
        m[4][1] = 21;
    }
    catch(std::logic_error &e) { 
        std::cerr << e.what(); 
    }

    return 0;
}

因此,当我们创建一个矩阵时,我们将其大小保存在rows和中cols。当我们operator[]在矩阵上使用时,它不会尝试直接返回对矩阵中项目的引用——而是返回一个代理类的实例,该类跟踪行和矩阵,提供operator[]其自己的。

因此,当您使用 时matrix[a][b],第一个只是保存amatrix进入代理对象。然后[b]在该代理对象上调用该部分。这会检查ab都在我们为矩阵保存的范围内,如果是,则返回对向量中正确对象的引用。否则,它会抛出一个std::Logic_error(可能不是最好的选择——只是我想到的第一个)。

我应该补充一点,这个一般想法有很多变化。仅举一个例子,您可以在编译时指定数组的大小,但将大小作为模板参数传递。这可能有一些优点——例如,matrix<int, 2, 3>它们matrix<int, 3, 2>是完全不同的类型,所以你不能不小心将一个分配给另一个。它也可能有一些缺点(最明显的是,您需要在编译时知道大小,否则它根本不起作用)。

于 2013-03-14T05:53:34.940 回答