21-14 运算符重载与函数模板

在第11.7节——函数模板实例化中,我们讨论了编译器如何利用函数模板来实例化函数,这些函数随后会被编译。我们还指出,如果函数模板中的代码试图执行实际类型不支持的操作(例如将整数值1添加到std::string),这些函数可能无法编译。

本节将通过若干实例,展示因实际类类型不支持特定运算符导致实例化函数编译失败的情况,并说明如何定义这些运算符以使实例化函数能够成功编译。


运算符、函数调用和函数模板

首先,让我们创建一个简单的类:

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

并定义一个最大值函数模板:

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

现在,让我们看看当我们尝试对类型为 Cents 的对象调用 max() 时会发生什么:

#include <iostream>

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

int main()
{
    Cents nickel{ 5 };
    Cents dime{ 10 };

    Cents bigger { max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

image

C++ 将为 max() 创建一个模板实例,其形式如下:

template <>
const Cents& max(const Cents& x, const Cents& y)
{
    return (x < y) ? y : x;
}

然后它将尝试编译这个函数。看出问题所在了吗?当x和y都是Cents类型时,C++根本不知道如何评估x < y!因此这会导致编译错误。

要解决这个问题,只需为任何需要使用max的类重载operator<运算符:

#include <iostream>

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend bool operator< (const Cents& c1, const Cents& c2)
    {
        return (c1.m_cents < c2.m_cents);
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x < y) ? y : x;
}

int main()
{
    Cents nickel{ 5 };
    Cents dime { 10 };

    Cents bigger { max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

这按预期运行,并输出:

image


另一个示例

让我们再看一个因缺少重载运算符而导致函数模板无法正常工作的示例。

以下函数模板将计算数组中若干对象的平均值:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

int main()
{
    int intArray[] { 5, 3, 2, 1, 4 };
    std::cout << average(intArray, 5) << '\n';

    double doubleArray[] { 3.12, 3.45, 9.23, 6.34 };
    std::cout << average(doubleArray, 4) << '\n';

    return 0;
}

这将产生以下值:

image

如你所见,它对内置类型效果极佳!

现在让我们看看当我们在 Cents 类上调用此函数时会发生什么:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

编译器彻底失控,吐出一大堆错误信息!第一个错误信息大概是这样的:

image

请记住,average() 返回的是一个 Cents 对象,而我们正试图使用 operator<< 将该对象流式传输到 std::cout。然而,我们尚未为 Cents 类定义 operator<<。现在来实现它:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

如果我们再次编译,将会得到另一个错误:

image

该错误实际上是由调用 average(const Cents*, int) 时创建的函数模板实例引起的。请记住,当调用模板函数时,编译器会生成该函数的副本,其中模板类型参数(占位符类型)已被函数调用中的实际类型替换。以下是当 T 为 Cents 对象时 average() 的函数模板实例:

我们收到错误消息的原因在于以下这行代码:

sum += myArray[count];

在此情况下,sum 是 Cents 对象,但我们尚未为 Cents 对象定义 operator+= 运算符!我们需要定义此函数才能使 average() 能处理 Cents 对象。展望后续,我们发现 average() 还使用了 operator/= 运算符,因此我们将继续定义该运算符:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum { 0 };
    for (int count { 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents {};
public:
    Cents(int cents)
        : m_cents { cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }

    Cents& operator+= (const Cents &cents)
    {
        m_cents += cents.m_cents;
        return *this;
    }

    Cents& operator/= (int x)
    {
        m_cents /= x;
        return *this;
    }
};

int main()
{
    Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

最后,我们的代码终于能够编译并运行了!以下是运行结果:

image

请注意,我们根本无需修改 average() 函数就能使其支持 Cents 类型的对象。我们只需为 Cents 类定义实现 average() 所需的运算符,编译器便会自动完成其余工作!

posted @ 2026-01-26 10:57  游翔  阅读(1)  评论(0)    收藏  举报