为模版类增添友元函数的几种方法

《Effective C++ 3rd Edition》中的条款46提到了“需要类型转换时请为模版定义非成员函数”,其中涉及到模版类和友元函数,我之前还从没把这两者联系在一起过。既然看到这里,于是把相关的知识梳理了一下。为模版类添加的友元函数分为三类:

非模板友元
约束(bound)模板友元,即友元的类型取决于类被实例化时的类型。
非约束(undound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。

书中作者的实现方式采用了第一种,先看原始代码:

template <typename T>
class Rational
{
public:
    Rational(const T &numerator=0, const T &denominator=1):
        _numerator(numerator), _denominator(denominator) {}
    const T numerator() const { return _numerator; }
    const T denominator() const { return _denominator; }
private:
    T _numerator;
    T _denominator;
};

template <typename T>
const Rational<T> operator *(const Rational<T> &lhs, const Rational<T> &rhs)
{
    return Rational<T>(lhs.numerator() * rhs.numerator(),
                       lhs.denominator() * rhs.denominator());
}

template <typename T>
ostream& operator <<(ostream &os, const Rational<T> &rational)
{
    os << "Numerator: " << rational.numerator() <<
        "  Denominator: " << rational.denominator();
    return os;
}

int main(void)
{
    Rational<int> a(3, 5), b(2, 7);
    Rational<int> c = a * b;
    cout << c << endl;
    return 0;
}
当operator *仅仅是一个普通函数模版而非友元时,上述Rational<int>的对象a和b相乘没有问题,因为函数模版可以根据参数类型被具体化。然而改成a * 2就失败了,虽然整数2可以隐式转化为Rational<int>类型,但是编译器在进行模版匹配的时候不会考虑这一点(假如要纳入考虑,可以想象一下当参数有很多个又是不同类型,且都可以由其他类型隐式转化而来,编译器会有多头痛),所以我们在最好让 Rational<int> operator *(const Rational<int> &lhs, const Rational<int> &rhs) 这样的函数被声明出来,执行a * 2会直接和该函数适配,发现2可以隐式转化成Rational<int>,OK成功。那么最容易联想到的方法自然是声明成类的友元函数,这样Rational<T>被具体化到Rational<int>的过程中,这个函数也就被声明出来了,看代码:

template <typename T>
class Rational
{
public:
    Rational(const T &numerator=0, const T &denominator=1):
        _numerator(numerator), _denominator(denominator) {}
    const T numerator() const { return _numerator; }
    const T denominator() const { return _denominator; }
    friend const Rational operator *(const Rational &lhs, const Rational &rhs);
    //friend const Rational<T> operator *(const Rational<T> &lhs, const Rational<T> &rhs);
private:
    T _numerator;
    T _denominator;
};

template <typename T>
const Rational<T> operator *(const Rational<T> &lhs, const Rational<T> &rhs)
{
    return Rational<T>(lhs._numerator * rhs._numerator,
                       lhs._denominator * rhs._denominator);
}

template <typename T>
ostream& operator <<(ostream &os, const Rational<T> &rational)
{
    os << "Numerator: " << rational.numerator() <<
        "  Denominator: " << rational.denominator();
    return os;
}

int main(void)
{
    Rational<int> a(3, 5), b(2, 7);
    Rational<int> c = a * 2;
    cout << c << endl;
    return 0;
}
值得注意的是上述代码中注释掉的那一行友元函数的写法也是合理的,因为函数声明本身在Rational模版类内部,所以返回值和参数用到的Rational<T>可以省去<T>。重点来了,这段代码虽然能通过编译却无法成功链接。可能很多人和我一样,开始都会不假思索的认为operator *函数模版的定义也会被具体化,其实不然。友元函数虽然写在类里面,但是它并非一个成员函数,当类被具体化的时候,外面定义的operator *函数模版还是原封不动的,自然就无法链接了,除非完整实现一份当特化为int类型的友元函数。于是最后的方案是将定义的部分也挪进类里面,如果一定要写在外面,那只好把整个函数的定义写出来了,对不同的T需要写多份,很麻烦。

template <typename T>
class Rational
{
public:
    Rational(const T &numerator=0, const T &denominator=1):
        _numerator(numerator), _denominator(denominator) {}
    const T numerator() const { return _numerator; }
    const T denominator() const { return _denominator; }
    friend const Rational operator *(const Rational &lhs, const Rational &rhs)
    {
        return Rational(lhs._numerator * rhs._numerator,
                        lhs._denominator * rhs._denominator);
    }
private:
    T _numerator;
    T _denominator;
};

接下来再看看另外两种友元函数的实现,他们均被定义成函数模版。先看约束模版友元,虽然函数模版可以被具体化为多种函数,但只有指定的一种才能成为类的朋友,也就是说在声明时直接指定一个类型,像下面这样:

template <typename T>
class Rational;


template <typename T>
const Rational<T> operator *(const Rational<T> &, const Rational<T> &);

template <typename T>
class Rational
{
public:
    Rational(const T &numerator=0, const T &denominator=1):
        _numerator(numerator), _denominator(denominator) {}
    const T numerator() const { return _numerator; }
    const T denominator() const { return _denominator; }
    friend const Rational<T> operator * <>(const Rational<T> &lhs, const Rational<T> &rhs);

private:
    T _numerator;
    T _denominator;
};

template <typename T>
const Rational<T> operator *(const Rational<T> &lhs, const Rational<T> &rhs)
{
    return Rational<T>(lhs._numerator * rhs._numerator,
                       lhs._denominator * rhs._denominator);
}

template <typename T>
ostream& operator <<(ostream &os, const Rational<T> &rational)
{
    os << "Numerator: " << rational.numerator() <<
        "  Denominator: " << rational.denominator();
    return os;
}

int main(void)
{
    Rational<int> a(3, 5), b(2, 7);
    Rational<int> c = a * b;
    cout << c << endl;
    return 0;
}

有几点值得注意的。既然声明的友元是个特化的函数,那么之前必须先前置声明该函数模版,否则编译器就不认识了。声明的友元函数里面有个尖括号<>,其实是省略了类型T,因为通过函数的参数和返回值可以进行推导,倘若是个类似 void fun(void) 的友元函数,那么就得写明。这里我们指明了函数模版用类型T来特化,这就取决于模版类的特化,当然,如果有需要也可以特化为<int>或<char>或者其他。和前面那个例子不同的是,这时函数不能定义在类里面,我用的VC2008编译器会报告  Error C3637: a friend function definition cannot be a specialization of a function type。也就是说,上面这个方法,无法支持a * 2这种运算,最多支持a * b。个人认为约束模版友元也有其意义,比如一个模版类A<T>和函数模版fun<U>,当T为内置数值类型时,授权fun<CLASS_X>访问,当T为用户自定义类型时,授权fun<CLASS_Y>访问,这样就可以灵活授权给不同的友元函数,实现差异化的需求。

最后是非约束模版友元,在下面的例子里,类模版的类型为T,友元函数模版的类型为U,两者不相干,任意一个将U具体化的operator *函数,都是任何一个将T具体化的Rational类的朋友,这是一种“多对多”的关系。和上一个例子一样,函数模版的定义部分在类的外部,进行匹配时编译器不会考虑隐式转化,所以也无法支持a * 2这样的语句。

template <typename T>
class Rational
{
public:
    Rational(const T &numerator=0, const T &denominator=1):
        _numerator(numerator), _denominator(denominator) {}
    const T numerator() const { return _numerator; }
    const T denominator() const { return _denominator; }
    template <typename U>
    friend const Rational<U> operator *(const Rational<U> &lhs, const Rational<U> &rhs);

private:
    T _numerator;
    T _denominator;
};

template <typename U>
const Rational<U> operator *(const Rational<U> &lhs, const Rational<U> &rhs)
{
    return Rational<U>(lhs._numerator * rhs._numerator,
                       lhs._denominator * rhs._denominator);
}

template <typename T>
ostream& operator <<(ostream &os, const Rational<T> &rational)
{
    os << "Numerator: " << rational.numerator() <<
        "  Denominator: " << rational.denominator();
    return os;
}

模版类的友元函数有这么几种情况,如果需要声明的是一个友元类,且这个类有可能是一个模版,那么同样会有多种情况。总之,有了模版这个东西,C++更强大,也更复杂了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值