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;
    }
};

并定义一个max函数模板

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;
}

C++将为max()创建一个模板实例,如下所示

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

然后它会尝试编译这个函数。看到问题了吗?当xyCents类型时,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;
}

这按预期工作,并打印出

10 is bigger

另一个例子

我们再看一个函数模板因缺少重载运算符而无法工作的例子。

以下函数模板将计算数组中多个对象的平均值

#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;
}

这产生的值是

3
5.535

正如你所看到的,它对内置类型工作得很好!

现在让我们看看当我们对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;
}

编译器会疯狂地产生大量的错误消息!第一个错误消息会是这样的

error C2679: binary << : no operator found which takes a right-hand operand of type Cents (or there is no acceptable conversion)

记住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;
}

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

error C2676: binary += : Cents does not define this operator or a conversion to a type acceptable to the predefined operator

此错误实际上是由我们调用average(const Cents*, int)时创建的函数模板实例引起的。请记住,当我们调用模板函数时,编译器会“模板化”一个函数的副本,其中模板类型参数(占位符类型)已被函数调用中的实际类型替换。这是当TCents对象时average()的函数模板实例

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

    sum /= numValues;
    return sum;
}

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

        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;
}

最后,我们的代码将编译并运行!结果如下

11 cents

请注意,我们根本不需要修改average()就能使其与Cents类型的对象一起工作。我们只需为Cents类定义用于实现average()的运算符,其余的由编译器处理!

guest
您的电子邮箱地址将不会被显示
发现错误?请在上方留言!
与勘误相关的评论在处理后将被删除,以帮助减少混乱。感谢您帮助使网站对每个人都更好!
来自 https://gravatar.com/ 的头像与您提供的电子邮箱地址相关联。
有回复时通知我:  
219 评论
最新
最早 最多投票
内联反馈
查看所有评论