章节目录

重载括号运算符

本节阅读量:

到目前为止,您看到的所有重载操作符都允许您定义参数的类型,但不能定义参数的数量(根据操作符的类型固定)。例如,operator==总是采用两个参数,而operator!总是要一个参数。括号操作符( operator() )是一个特别有趣的操作符,它允许您改变它所采用的参数的类型和数量。

有两件事需要记住:首先,括号操作符必须实现为成员函数。其次,在非面向对象的C++中,「operator()」用于调用函数。在类中,「operator()」只是一个普通的操作符,它像任何其他重载操作符一样调用函数(只是命名为「operator()」 )。


一个例子

让我们看一个示例:

1
2
3
4
5
class Matrix
{
private:
    double data[4][4]{};
};

矩阵是线性代数的一个关键组件,通常用于进行几何建模和3D图形。在上面的示例,Matrix类是一个4×4的二维double数组。

我们可以重载「operator[]」以提供对专用一维数组的直接访问。然而,在上面的情况下,希望访问private二维数组。由于「operator[]」仅限于单个参数,因此是无能为力的。

然而,由于「operator()」可以接受任意多的参数,因此我们可以声明一个接受两个整数索引参数的「operator()」版本,并使用它访问二维数组。下面是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <cassert> // for assert()

class Matrix
{
private:
    double m_data[4][4]{};

public:
    double& operator()(int row, int col);
    double operator()(int row, int col) const; // for const 对象
};

double& Matrix::operator()(int row, int col)
{
    assert(row >= 0 && row < 4);
    assert(col >= 0 && col < 4);

    return m_data[row][col];
}

double Matrix::operator()(int row, int col) const
{
    assert(row >= 0 && row < 4);
    assert(col >= 0 && col < 4);

    return m_data[row][col];
}

现在,我们可以声明矩阵并像这样访问其元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

int main()
{
    Matrix matrix;
    matrix(1, 2) = 4.5;
    std::cout << matrix(1, 2) << '\n';

    return 0;
}

产生结果:

1
4.5

现在,让我们再次重载「operator()」,这一次新增一个不需要参数的重载函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cassert> // for assert()
class Matrix
{
private:
    double m_data[4][4]{};

public:
    double& operator()(int row, int col);
    double operator()(int row, int col) const;
    void operator()();
};

double& Matrix::operator()(int row, int col)
{
    assert(row >= 0 && row < 4);
    assert(col >= 0 && col < 4);

    return m_data[row][col];
}

double Matrix::operator()(int row, int col) const
{
    assert(row >= 0 && row < 4);
    assert(col >= 0 && col < 4);

    return m_data[row][col];
}

void Matrix::operator()()
{
    // 将所有的值重置为 0.0
    for (int row{ 0 }; row < 4; ++row)
    {
        for (int col{ 0 }; col < 4; ++col)
        {
            m_data[row][col] = 0.0;
        }
    }
}

下面是新示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>

int main()
{
    Matrix matrix{};
    matrix(1, 2) = 4.5;
    matrix(); // 清空matrix
    std::cout << matrix(1, 2) << '\n';

    return 0;
}

产生结果:

1
0.0

由于「operator()」非常灵活,因此很容易将其用于许多不同的目的。然而,强烈不建议这样做,因为「()」并不能真正指示操作符正在做什么。在上面的示例中,最好将清空功能编写为一个名为 clear() 或 erase() 的函数,因为 matrix.erase() 比 matrix() 的行为更容易理解。


函子(函数对象)

「operator()」通常也被重载以实现functor(函子,即函数对象),这是像函数一样操作的类对象。函子比普通函数的优点是函子可以将数据存储在成员变量中(因为它们是类的对象)。

这里有一个简单的函子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class Accumulator
{
private:
    int m_counter{ 0 };

public:
    int operator() (int i) { return (m_counter += i); }

    void reset() { m_counter = 0; } 
};

int main()
{
    Accumulator acc{};
    std::cout << acc(1) << '\n'; // 打印 1
    std::cout << acc(3) << '\n'; // 打印 4

    Accumulator acc2{};
    std::cout << acc2(10) << '\n'; // 打印 10
    std::cout << acc2(20) << '\n'; // 打印 30
    
    return 0;
}

请注意,使用Accumulator看起来就像进行普通函数调用,但acc对象存储的是累积的值。

关于函子的好处是,可以根据需要实例化任意多个单独的函子对象,并同时使用它们。函子也可以有其他成员函数(例如, reset() )来执行方便的操作。


结论

「operator()」有时会重载两个参数来索引多维数组。但其它任何需求,都可以更好的实现为使用可读的有函数名的成员函数。

「operator()」也经常重载以创建函子。尽管简单的函子(如上面的示例)相当容易理解,但函子通常用于更高级的编程主题,有需要再学习即可。


21.8 重载下标运算符

上一节

21.10 重载类型转换

下一节