EffectiveC++-条款24:若所有参数皆需类型转换,请为此采用 non-member函数

本文讨论了在C++中为类如Rational设计operator*时遇到的问题,即如何支持隐式转换和混合运算。通过提供non-member函数实现operator*,使得每个参数都可以进行隐式转换,从而解决int与Rational的混合运算问题。同时,文章强调了避免不必要的friend函数,因为它们可能导致更多的复杂性和潜在问题。
摘要由CSDN通过智能技术生成

一. 内容

  1. 在导读中提过,令 classes 支持隐式转换是一个糟糕的主意,当然这有例外。比如当你建立一个数值类型,允许整数隐式转换为有理数似乎是合理的。好比 C++ 允许 int 隐式转换为 double,double 隐式转换为 int。

  2. 举个例子,假如你要编写一个分数 Rational 类。

    class Rational {
    public:
        Rational(int mNumerator = 0, int mDenominator = 1):
            Numerator(mNumerator), Denominator(mDenominator) {}
    
    public:
        int GetNumerator() const {
            return Numerator;
        }
    
        int GetDenominator() const {
            return Denominator;
        }
    
    public:
        Rational operator*(const Rational& Other) const {
            return Rational(GetNumerator() * Other.GetNumerator(), GetDenominator() * Other.GetDenominator());
        }
    
    private:
        int Numerator;
        int Denominator;
    };
    
    
  3. 对于分数的乘法实现,你可能在考虑是由 member 函数,还是 non-member,friend 函数,但基于面向对象的精神,你直觉告诉你,可以把实现放到类中重载 operator*。尽管在条款23反直觉的主张 non-member 也许比 member 函数更符合面向对象守则。但我们先放一放。

    在类中重载 operator*,你可以轻易的实现

    const Rational TempOne(1, 8);
    const Rational TempTwo(1, 2);
    Rational Result = TempOne * TempTwo; //很好
    Result = Result * TempOne; //很好
    

    但不够,你可能还希望支持混合运算,也就是拿 Rational 与其他 int 相乘。

    Result = Result * 2;//很好,2隐式转换为Rational
    Result=2*Result;//错误
    //写成函数调用可以看出问题
    //Result=Result.operator*(2);
    //Result=2.operator*(Result);
    

    但很快你发现,int * Rational 出现错误,但我们自然的认为乘法应该符合交换律。当你写成函数调用时,可以看出问题,Result=2.operator*(Result),2并不是一个class,也就每不存在 operator* 成员函数。编译器会尝试在全局作用域寻找这样的 non-member 函数 operator*(2,Result),但本例并不存在,所以查找失败。

    这中间有一点需要注意,为什么前者可以通过,而后者不可以通过?因为前者发生了隐式转换,编译器知道你传递了2(int),而函数需要的是 Rational,但它也知道只要调用 Rational 构造函数并赋予你所提供的2(int),就可以变出一个适当的 Rational 出来,于是它就那样做了。当然这是当你没有使用 explicit 关键字的情况下,因为explicit会阻止构造函数或转换函数进行隐式转换

    但是为什么后者的 2(int)不能进行隐式转换,注意,只有当参数处于参数列时,这个参数才能进行隐式转换。正如上面所述,必须在其他作用域存在 operator(Rational,Rational) 时,2才能被作为参数,合理的进行隐式转换,否则它的地位相当于被调用函数的那个对象。

  4. 那么怎样支持混合式运算呢?答案呼之欲出,拨云见日:使用 non-member 函数,使其每个实参可以进行隐式转换。

    inline Rational operator*(const Rational& RationalOne,const Rational& RationalTwo){
        return  Rational(RationalOne.GetNumerator() * RationalTwo.GetNumerator(),
            RationalOne.GetDenominator() * RationalTwo.GetDenominator());
    }
    

    这样混合式运算就成功通过编译并且运行成功。

  5. 这是一个快乐的结局,但还有一点需要关心。non-member operator*是否有必要成为 friend 函数呢?就本例而言,答案是否定的,因为 Rational 提供的 public 接口已经可以完成所有的任务。这也给我们一个提示:member函数的反面是 non-member 函数,而不是 friend 函数。大多 C++ 程序员假如,如果一个函数不是一个 member 函数,那么它就该是一个 friend。这往往是不必要的,而且无论何时,都尽量避免 friend 函数,因为就像真实世界里那样,朋友带来的麻烦往往多过其价值。

  6. 本条款蕴含真理,但不是全部的真理。特别是当你从 Object-oriented C++ 转移到 Template C++,将 Rational 设计成 Rational Template 时,又会有新的争议,问题,解法。

二. 总结

  1. 如果你需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个 non-member。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值