C++类型转换时定义非成员函数

C++类型转换时定义非成员函数,第1张

C++类型转换时定义非成员函数,第2张

C++的箴言:声明为非成员函数的时候解释了为什么只有非成员函数才适合应用于所有参数的隐式类型转换,并且还使用了Rational类的一个operator*函数作为例子。我建议您在阅读本文之前熟悉一下那个例子,因为本文对C++箴言中的例子做了一个无害的扩展讨论(模板化Rational和operator*):声明为非成员函数的时间:

template
class rational {
public:
rational(const t & enumerator = 0,//有关为什么params
const t & denominator = 1,请参见“c++箴言:引用到const而不是传递值”;//现在通过引用传递

const T分子()const;//关于为什么返回
const t denominator () const,参见《c++箴言:避免返回对象内部组件的句柄》;//值仍然通过值传递,
...//第3项了解它们为什么是const
};

模板
const Rational运算符*(const Rational& lhs,
const Rational & RHS)
{...}

就像《C++:声明一个非成员函数的时候》里说的,我想支持混合模式算术,所以要让下面的代码编译。我们希望如此,因为我们使用了第24项中的相同代码。唯一的区别是Rational和operator*现在是模板:

有理二分之一(1,2);//这个例子来自c++箴言:声明非成员函数的时候,
//除了rational现在是模板

有理结果= one half * 2;//错误!不会编译

编译失败的事实暗示了模板化Rational的非模板(non-template)版本有所不同,而且确实存在。在《C++:声明非成员函数的时候》中,编译器知道我们要调用什么函数(得到两个有理数的运算符*),但是在这里,编译器不知道我们要调用哪个函数。相反,他们试图从名为operator*的模板中确定要实例化(即创建)的函数。他们知道他们假设名为operator*的实例化函数接受两个Rational类型参数,但是为了进行这个实例化,他们必须确定t是什么。问题是,他们不能。

在尝试推导t时,他们将检查传递给操作符*的调用的参数类型。在当前的例子中,类型是Rational(半类型)和Int(2类型)。每个参数都要单独检查。

使用oneHalf的推导非常简单。operator*的第一个参数声明为Rational类型,而传入operator*的第一个实参(oneHalf)是Rational类型,所以t必须是int。可惜其他参数的推导没那么简单。operator*的第二个参数声明为Rational type,但是传入了operator*的第二个参数(argument) (2)的int类型。在这种情况下,编译器如何确定t是什么?你可能期望他们使用Rational的非显式构造函数将2转换成一个Rational,这样他们就可以推断出T是int,但是他们没有。他们不这么做是因为在模板实参推演的过程中从来不考虑隐式类型转换函数。从来没有。这个转换可以用在函数调用过程中,这是真的,但是在调用一个函数之前,你必须知道哪个函数存在。为了了解这一点,您必须推导出相关函数模板的参数类型(以便您可以实例化适当的函数)。但是在模板实参推演过程中,并没有考虑到通过构造函数调用的隐式类型转换。C++的箴言:声明一个非成员函数的时候不包含模板,所以模板参数推导不是问题。现在我们在C++的模板部分,这是主要问题。

模板类中的友元声明可以引用一个特定的函数,我们可以利用这个事实来解决编译器对模板参数推导的挑战。这意味着Rational类可以将操作符*声明为Rational的友元函数。类模板不依赖于模板实参推导(这个过程只适用于函数模板),所以t在类Rational被实例化时总是已知的。通过将适当的操作符*声明为Rational类的朋友,使它变得简单:

模板
类Rational {
public:
...
friend//declare operator *
const Rational operator *(const Rational & lhs,// function(参见
const Rational & RHS);//下面详细)
};

模板//定义运算符*
const Rational运算符*(const Rational& lhs,//函数
const Rational & RHS)
{...}

现在我们对operator*的混合模式调用可以编译了,因为当object oneHalf被声明为Rational类型时,类Rational被实例化,作为这个过程的一部分,取Rational参数(形参)的友元函数operator*被自动声明。作为一个声明的函数(不是函数模板),编译器在调用它的时候可以使用隐式转换函数(比如Rational的非显式构造函数),而这就是它们如何让混合模式调用成功的。

唉,在这里的语境中,“成功”是一个可笑的词,因为代码虽然可以编译,却无法连接。但是我们以后会处理的。首先,我想讨论用于在Rational中声明operator*的语法。

在类模板中,模板的名称可以用作模板及其参数的缩写。因此,在Rational内部,我们可以只写Rational而不写Rational。在这个例子中,它只为我们节省了几个字符,但当有多个参数或更长的参数名时,它不仅可以节省击键次数,还可以使最终的代码显得更清晰。我之所以提出这一点,是因为operator*被声明为get和return Rationals,而不是Rationals。它与声明运算符*一样合法,如下所示:

模板
类Rational {
public:
...
friend
const Rational算子*(const Rational& lhs,
const Rational & RHS);
...
};

然而,使用缩写更简单(也更常用)。
现在回到连接问题。混合模式代码编译,因为编译器知道我们要调用一个特定的函数(得到一个Rational和一个Rational操作符*),但是那个函数只在Rational内部声明,这里没有定义。我们的意图是让类外的operator*模板提供这个定义,但是这个方法不行。如果我们自己声明一个函数(这就是我们在Rational template中所做的),定义这个函数就是我们的责任。现在的情况是我们没有提供定义,这也是连接器找不到的原因。

也许最简单的方法是将operator*的本体合并到它的声明中:

模板
类Rational {
public:
...

友元const Rational运算符*(const Rational& lhs,const Rational & RHS)
{
return Rational(lhs . enumerator()* RHS . enumerator(),//same impl
lhs . denominator()* RHS . denominator());//如在
}//c++的箴言:声明非成员函数的时间
};

事实上,这正如预期的那样工作:对operator*的混合模式调用现在可以编译、连接和运行了。万岁!

关于这项技术的一个有趣的观察结论是,友谊的使用对访问类的非公共部分(非公共组件)的需求没有影响。为了使所有参数的类型转换成为可能,我们需要一个非成员函数(C++的箴言:声明非成员函数的时间仍然适用);为了自动实例化一个适当的函数,我们需要在类中声明这个函数。在一个类中声明一个非成员函数的方法是使它成为一个友元。这就是我们所做的。是不是反传统?是的,有效吗?这是毫无疑问的。

正如《C++的箴言:理解内联的介入和排斥》中所述,类内定义的函数被隐式声明为内联,这也包括像operator*这样的友元函数。您可以让operator*什么都不做,只调用一个在这个类之外定义的helper函数,从而将这种内联声明的影响降到最低。在本文的这个例子中,没有特别指出这一点,因为operator*已经可以实现为单行函数(单行函数),但是对于更复杂的函数体,这可能是合适的。“让朋友调用助手”(“让朋友调用辅助函数”)的方法值得注意。

Rational是一个模板的事实意味着helper function通常是一个模板,所以在头文件中定义Rational的代码通常是这样的:

模板类Rational//declare
//Rational
//template
template//declare
const Rational do multiply(const Rational & lhs,//helper
const Rational & RHS);//template
template
class Rational {
public:
...

friend
const Rational算子*(const Rational& lhs,
const Rational & RHS)//Have friend
{ return do multiply(lhs,RHS);} //调用helper
...
};

大多数编译器基本上强迫你把所有的模板定义放在头文件中,所以你可能还需要在头文件中定义doMultiply。(如第30项所述,这种模板不需要内联。)它可能看起来像这样:

template//define
const Rational do multiply(const Rational & lhs,//helper
const Rational & RHS)//template in
{//头文件,
return Rational(lhs . enumerator()* RHS . enumerator(),//如有必要
lhs . denominator()* RHS . denominator());
}

当然,作为模板,doMultiply不支持混合模式乘法,但也不是必须的。仅由operator*调用,operator*支持混合模式操作!本质上,函数operator*支持确保两个Rational对象相乘所需的所有类型转换,然后它将这两个对象传递给doMultiply模板(template)的适当实例化,以进行实际的相乘。合作,不是吗?

要记住的事情

当编写一个提供类模板的模板,并且这个类模板提供了一个函数,这个函数是指支持所有参数隐式类型转换的模板,在类模板内部定义这些函数为友元(friends)。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » C++类型转换时定义非成员函数

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情