章节目录

重载括号运算符

本节阅读量:

到目前为止,您看到的所有重载运算符都允许您定义参数类型,但不能定义参数数量(参数数量由运算符类型决定)。例如,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 重载类型转换

下一节