条款46: 需要隐式类型转换时请为模板定义非成员函数

这个条款可以看成是条款24的续集,我们先简单回顾一下条款24,它说了为什么类似于operator *这样的重载运算符要定义成非成员函数(是为了保证混合乘法2*SomeRational或者SomeRational*2都可以通过编译,2不能同时进行隐式类型转换成某个Rational,再作this用)。

所以我们一般(并非不定义成友元就编译不过,而是为了更合理)将之定义成友元函数,像下面这样:

class Rational
{
private:
    int numerator;
    int denominator;
public:
    Rational(int n = 0, int d = 1): numerator(n), denominator(d){assert(denominator != 0);}
    int GetNumerator() const{return numerator;}
    int GetDenominator() const {return denominator;}
    friend const Rational operator* (const Rational& r1, const Rational& r2);
};
const Rational operator* (const Rational& r1, const Rational& r2)
{
    return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
}

现在我们来引入模板,可以像下面这样写,注意这里的operator*是一个独立的模板函数:

template <class T>
class Rational
{
private:
    T Numerator;
    T Denominator;

public:
    Rational(const T& Num = 0, const T& Den = 1) : Numerator(Num), Denominator(Den){}
    const T GetNumerator() const
    {
        return Numerator;
    }

    const T GetDenominator() const
    {
        return Denominator;
    }

    string ToString() const
    {
        stringstream ss;
        ss << Numerator << "/" << Denominator;
        return ss.str();
    }
};

template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
    return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
        a.GetDenominator() * b.GetDenominator() );
}

但下面main函数的两行却都不能通过编译:

int main()
{
    Rational<int> a(3, 5);
    Rational<int> c = a * 2; // 不能通过编译!
    c = 2 * a;               // 不能通过编译!
    cout << c.ToString() << endl;
}

原因是编译器推导T出现了困难,a * 2在编译器看来,可以由a是Rational<int>将T推导成int,但是2是什么,理想情况下编译器会尝试将它先转换成一个Rational<int>,并将T推导成int,但事实上编译器在“T推导过程中从不将隐式类型转换函数纳入考虑”。所以无论是a * 2还是2 * a都是不能通过编译的,一句话,隐式转换+推导T不能被同时被编译器接受。

解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先满足好一个条件,再由编译器执行另一个过程好了。

如果把这个operator*在template class里面进行声明,就可以了。这是因为,a被声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。

因此我们可以这样来改:

template <class T>
class Rational
{
    …
    /* 在一个class template内,template名称可被用来作为“template和其参数”
    * 的简略表达式,所以在Rational<T>内我们可以只写Rational而不必写
    * Rational<T> 
    */
    friend Rational operator* (const Rational& a, const Rational& b);
};

template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
    // 这里友元函数的声明并不是用来访问类的私有成员的,而是用来进行事先类型推导的
    return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
        a.GetDenominator() * b.GetDenominator() );
}

注意声明部分,我们添加了一个友元函数的声明,果然编译通过了,但链接时又报错了,原因是链接器找不到operator*的定义,这里又要说模板类中的一个特殊情况了,它不同与普通的类,模板类的友元函数只能在类中实现,所以要把函数体部分移至到类内,像下面这样:

template <class T>
class Rational
{
    …
    friend Rational operator* (const Rational& a, const Rational& b)
    {
        return Rational (a.GetNumerator() * b.GetNumerator(),
            a.GetDenominator() * b.GetDenominator());
    }
    …
}

这下编译和链接都没有问题了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容