当前位置:天才代写 > tutorial > C语言/C++ 教程 > 泛型编程:再现Min和Max

泛型编程:再现Min和Max

2017-11-04 08:00 星期六 所属: C语言/C++ 教程 浏览:350

副标题#e#

在1995年1月,Scott Meyers 在C++ Report杂志上就强调"min,max 对C++社团来说是一个很大的挑战",他对基于macro-based实现的min,max举办当真的阐明,比较基于模板的min,max实现,他获得了如下结论:

“对付实现max,什么是最好的要领?”用一句Tevye的名言:“我将汇报你:我不知道”,基于以上阐明,我有信心汇报各人宏实现的要领,也许是最好的,不外,我小我私家很讨厌:宏。因此,各人假如有什么更好的实现要领,请赶紧汇报我。

按照我小我私家的常识,在本日这个时候,这个挑战依然存在,在这篇文章中,我将面临这个挑战,可是,在我开始之前,让我们往返想一下以前泛型编程,是如何糟糕实现这个算法的。

自从"Generic<Programming>: volatile – Multithreaded Programmer”s Best Friend" 颁发后,我接到许多邮件。在这些邮件中,我接到许多的奖赏,同时,也有许多对the Usenet newsgroups comp.lang.c++新闻组和 comp.programming.threads的诉苦。这个接头较量剧烈,假如,你有乐趣,你可以去介入。标题是:"volatile, was: memory visibility between threads."一个接头。

在这些接头中,我以为,我学到许多的常识,在这里许多独立的例子,好了,长话短说,许多系统不会修改volatile 数据(譬喻在POSIX编译系统中),同时在别的的一些系统中,插手volatile量,也无助于措施的质量提高,关于volatile mutexes正确利用的最重要问题,是它依赖于雷同于posix mutexes,同时,在多历程系统中,这种互斥量往往是不足的,因此,你必需利用内存掩护。

别的一个更具有哲学意义的问题是,严格的讲,volatile法则远离变量是不公道的,就算你添加的volatile切合你应用volatile的法则。

一个系统能存储volatile数据在差异的位置,不像non-volatile数据那样,因此,牢靠其存储地点将使得系统不不变。volatile的正确性也是别的一个被品评的工具,尽量它可以在低程度的race condition,可是,不能在更高的条理检测逻辑上的race condition。譬喻,你有一个像std::vector的类mt_vector,同时尚有一些同步的成员函数。

如下:

volatile mt_vector<int> vec;
...
if (!vec.empty()) {
vec.pop_back();
}

本来的想法是从容器vector中移走最后一个元素,在单线程情况下,这段代码会运行的很好,可是,假如你在多线程利用这样的代码,往往会呈现异常的,就算你的empty(),pop_bock()已经适当的同步了。因此,可以说,低条理的数据一致性获得掩护,可是,更高程度的数据操纵,往往是不不变的。

至少,按照以上的阐明,我开始僵持我的求证volatile要领,这是个有效检测在雷同posix互斥量race conditions的好要领。可是,假如你从事多历程系统的内存存取权限布置的话,那么,你首先应该细听你的编译器文档资料。你应该知道你该干什么。Kenneth Chiu 提到一篇很有趣的论文: http://theory.stanford.edu/~freunds/race.ps, pape讲授了 "Type-Based Race Detection for Java."

由于Java范例系统只有很少的限制条件,这就使得编译器有大概和措施员一起来在编译时刻检测race conditions。

Min 和Max

来让我们再看看Scott提出的挑战,基于宏界说的min()如下:

#define min(a, b) ((a) < (b) ? (a) : (b))

由于它是完全范型的,因此,它经常能很好的发挥浸染。(只要这个表达式中 < 、?是有界说的)很不幸的是min()老是要计较它中间一个参数两次,这样,就导致许多令人狐疑的问题。(译注:如果如下挪用,a=3,b=5 int c=min(a++,b);功效我们会获得c=4的功效。显然不是我们所但愿的)宏界说只是在外貌形式上跟函数相像,可是,它的行为往往跟函数两个样。(假如你想获得更多的相关常识,那么,你可以查阅Herb的有关这方面的资料)在C++尺度库中有一个越发有效的实现,它是基于模板的办理方案,如下:

const T& min(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}

你可以看出这个方案中参数和返回值都是const型,这也就导致一个问题。也许,你想把两个数值中小的谁人的数值加2,那么,你也许会这么写:

double a, b;
...
min(a, b) += 2;

基于宏界说min()就不会呈现问题,可是,假如是模板基于模板上实现就不会那么听话了。因为你是不能改变const变量的值的,如同Scott所留意的一样。我们来更新我们的实现:

T& min(T& lhs, T& rhs)
{
return lhs < rhs ? lhs : rhs;
}


#p#副标题#e#

可是,这样照旧不能令人满足。因为编译器不能处理惩罚殽杂范例-一个参数是const,一个是non-const。

譬喻:

int a;
short int b;
...
int smallest =min(a,b);// error: can''t figure out T
// in template instantiation

不消说,基于宏的min()在这样的环境下又一次表示精采。基于模板的min(),必需这样写:

int smallest = min(a, int(b)); // aha, T is int

要想提供一个好的min/max,我们首先,要使得行为很像macros,同时,要制止macros带来的贫苦。

下面是一个智慧实现的开始:

#p#分页标题#e#

emplate <class L, class R>
class MinResult {
L& lhs_;
R& rhs_;
public:
operator L&() { return lhs_ < rhs_ ? lhs_ : rhs_; }
operator R&() { return lhs_ < rhs_ ? lhs_ : rhs_; }
MinResult(L& lhs, R& rhs) : lhs_(lhs), rhs_(rhs) {}
};
template <class LR>
class MinResult<LR, LR> {
LR& lhs_;
LR& rhs_;
public:
operator LR() { return lhs_ < rhs_ ? lhs_ : rhs_; }
MinResult(LR& lhs, LR& rhs) : lhs_(lhs), rhs_(rhs) {}
};
template <class L, class R>
MinResult min(L lhs, R rhs)
{
return MinResult(lhs, rhs);
}

为了把两个转换符统一为一个,在MinResult<LR,LR>要举办一些非凡局部处理惩罚。否则,L&,R&操纵会被认为反复界说。这个方案推迟了计较,直到它需要的时候,同时,在功效取出之前,它表示了精采的惰性原则。譬喻:

int a, b;
...
min(a, b);

实际上,上面的代码,没有做此外工作,同时,假如,你对范例有许多的思考的话,那么这样的代码将使得你对丛林中的树的观念有着更多的思考。另一方面,假如,你这么利用:

int c = min (a,b);

那么,编译器将挪用int&来转化min返回值(MinResult<int,int>)的姑且变量。这个强制转化可以或许正确的事情,返回无误的功效。

尽量你可以或许办理完美的const相关问题了,可是MinResult还不是一个完美的方案。我们思量一下下面的代码:

int a;
short int b;
extern Fun(int);
extern Fun(short int);
...
Fun(min(a, b)); // error! Don''t know which overload to invoke!

#p#副标题#e#

MinResult<int,short int>支持两种转化:int&,short int&,功效是,编译器可以挪用平等的挪用任何一个fun()函数,在这一方面,基于宏的min()又顺利通过,它将挪用像你想象的一样Fun(int)函数。

Quest for a Type

那么奈何才气天才般的办理这个问题。假设有两个范例L和R,思量min(L,R)返回值的正确范例应该…。譬喻:L是char,R是int,毫无疑问,应该返回int。这样,我们可以清晰的为min()界说四个函数。

template <class L, class R>
MINTYPE(L, R)
Min(L& lhs, R& rhs)
{ return lhs < rhs ? lhs : rhs; }
template <class L, class R>
MINTYPE(const L, R)
Min(const L& lhs, R& rhs)
{ return lhs < rhs ? lhs : rhs; }
template <class L, class R>
MINTYPE(L, const R)
Min(L& lhs, const R& rhs)
{ return lhs < rhs ? lhs : rhs; }
template <class L, class R>
MINTYPE(const L, const R)
Min(const L& lhs, const R& rhs)
{ return lhs < rhs ? lhs : rhs; }

这四个重载的函数对应着在const和non-const之间四个大概的组合。这个主意不错,可是,我们该奈何来界说MINTYPE?我们知道有一个对应的技能traits,简直我们可以利用traits来完成MINTYPE好比:

#define MINTYPE(L, R) typename MinTraits<L, R>::Result
template <class L, class R> struct MinTraits;
// Specialization for the L == R case
template <class LR> struct MinTraits<LR, LR> {
typedef LR& Result;
};
// Specialization for bool and char
template <> struct MinTraits<bool, char> {
typedef char Result;
};
...

这样实现不错,可是你将不得不写许多糟糕的代码。由于有14数据范例,因此,你将不得不为了每一个组合编MinTraits。其实,你可以简化这个事情,就像macros一样,可是,这不是一个很完美的方案。

这个方案还没有完成,你必需思量到用户界说的类。

别的,奈何为基类,子类挪用Min()函数,看看下面的例子:

class Shape {
...
unsigned int Area() = 0;
};
bool operator<(const Shape& lhs, const Shape& rhs) {
return lhs.Area() < rhs.Area();
}
class Rectangle : public Shape { ... };
void Hatch(Shape& shape)
{
Rectangle frame;
...
Shape& smallest = Min(shape, frame);
... use smallest ...
}

#p#分页标题#e#

事实上, 假如,min()处理惩罚Rectangle那么它将返回一个Shape的引用,这样就欠好了。由于Rectangle会自动转化为Shape引用范例,这样,我们必需做许多工作。

同时,在上例中,我们也可以看出基于macro的min()函数也不会正常发挥浸染,看下例子:

shape < frame ? shape : frame

它将两个参数转化为同样的范例,所以它等同于

shape < frame ? shape :(Shape) frame

显然,这不是我们所但愿的(这种糟糕的行为叫slicing)

在这篇文章中实现的min(),能给以你像macro-based版本一样的min()成果,甚至更多。同时,它的实现或许只有80行(包罗Max)

#p#副标题#e#

我们再温热一下我们的coffee,同时,开始接头Loki。这些代码利用了Loki,Loki提高较量先进的标记操纵,Min()用到的Loki东西有:

1 Typelists Typelists提供正规的Lists,它存储范例,不存储数值。

界说如下:

typedef TYPELIST_3(float, double, long double) FloatingPointTypes;

FloatingPointTypes 中包括三个范例

假设界说一个typelist 变量FloatingPointTypes,和任意范例 T,你可以通过利用编译时刻算法 Loki::TL::IndexOf察觉T是否在 FloatingPointTypes 。

譬喻:

Loki::TL::IndexOf<FloatingPointTypes, double>::value将返回1,假如 T不在这个list中返回-1。

2。Select Class Template 它的完全说明在参考 7中,简朴的说,Select答允你选择一两个基于编译时刻的Boolean 常量。譬喻:

typedef Loki::Select<sizeof(wchar_t) <
sizeof(short int), wchar_t, short int>::Result
SmallInt;

SmallInt将是wchar_t,short int 中最小的整数范例。

3 TypeTraits 这是一个模板类它是用来揣度范例的,譬喻判定是否这个范例是指针,这个指针指向什么变量等等。我们在这之利用了NonConstType TypeTraits<T>::NonConstType 是用来转化掉const。

4 Conversion 在参考7 中有它的具体说明。

这个类可以用来检测是不是一个一范例可以隐转化为别的的一个范例。Conversion是整个实现的基本。

The MinMaxTraits Class Template

为了简化范例的计较,我成立了一个简朴的线性types布局。首先我把所以types以牢靠的顺序放入。同时,我假定返回值的范例将是list中接近底部的范例。界说如下:(不思量const)

namespace Private
{
typedef TYPELIST_14(
const bool,
const char,
const signed char,
const unsigned char,
const wchar_t,
const short int,
const unsigned short int,
const int,
const unsigned int,
const long int,
const unsigned long int,
const float,
const double,
const long double)
ArithTypes;
}
自己,unsigned types 应该在signed types后头, float 应该int 后头。譬喻

 

Min(long,double)那么返回功效应该是double范例,因为在list中double在long后头(译者:我们知道功效也应该是double)

#p#副标题#e#

此刻,一般的算法就可以计较Min()的功效范例了。假如你的参数范例为不是引用的T,R具有如下条件:

1 假设 功效为R。

2 假如R可以隐转化为L,那么可以把功效转化为L型。

3 假如在Private::ArithTypes中R在L的后头,那么,转化功效为R型,这一步要处理惩罚许多的数学转化。

4 假如在不要引入姑且变量的环境下,L&可以自动的转化为R&,那么功效转化为R&,这一步确保Min(frame,shape)将返回Shape&型。

5 假如在不要引入姑且变量的环境下,R&可以自动的转化为L&,那么功效转化为L&,

这一步确保Min(shape,frame)返回Shape&.

在这中间罪艰巨的一步是“不引入姑且变量的范例间的转化”。本质假如not-const T的引用可以转化为not-const U的引用的话,那么,T可以在引入姑且变量的环境下,转化为U。

The Min and Max Overloads

对应于const与not-const的参数的四个差异组合,这里有四个Min(),Max()函数重载了。为了制止在Shape/Rectangle例子中的Slicing问题,Min()有个差异于a<b?a:b的实现。如下:

template <class L, class R>
typename MinMaxTraits<L, R>::Result
Min(L& lhs, R& rhs)
{ if (lhs < rhs) return lhs; return rhs; }
template <class L, class R>
typename MinMaxTraits<const L, R>::Result
Min(const L& lhs, R& rhs)
{ if (lhs < rhs) return lhs; return rhs; }
... two more overloads ...
.. similar Max implementation ...
这两个返回语句确保正确的转化并且没有slicing现象。这四个重载函数可以处理惩罚如下的环境:Min(a +b, c + d) 、 Min(a +b, 5)。

 

阐明:

让我看看这个新的实现是奈何满意Scott Meyers”先生的要求的。他的要求如下:

1 函数在语意上要举办范例查抄。明明Min()满意。

2 支持const ,non-const 参数。由于重载了四个函数。Min()支持non-const const的参数的任何组合。

#p#分页标题#e#

3 支持差异参数范例。Min()很显然是支持差异的范例的,它做了大量的智能事情,这是macro和简朴模板所无法完成的。Min()消除了差异范例之间的不同。主动的举办范例转化。这个转化进程是把握在library编写人的手中。

4 不需要强制实例化,Min()不需要。

在差异的指针的环境下(Shape* ,Rectangle*)Min()也能表示的很好。

这是由于算法的第一步所实现的。

Min()的一个重要的特征是它可以一个算法来揣度功效范例,而这个算法是你可以编写的。

假如你发明这个算法有什么不满足的处所。你可以自行修改。

很遗憾的是这个实此刻一些编译器上无法通过,我知道代码是没有问题的。假如你有什么好的发起请接洽我。

#p#副标题#e#

Look Ahead in Anger

这些天我在看 The Age of Spiritual Machines by Ray Kurzweil [8]. Kurzweil 说充实证明,在2020阁下你将用不到$1000买到一个有人脑一样的呆板。在谁人时候人们大概再说“你看,在2001年,那些家伙在实现max()还那么坚苦,一个家伙还特此写了一篇论文,”

min()不重要吗,我不这么认为,最大化,最小化是数学上,现实糊口中两个简朴的观念。假如,措施语言不能表达这样简朴的观念,那么说明在语言的深处,还存在问题。

也许有小孩说“妈妈我不想学语法和字汇,我只想学写诗”这样能乐成吗?

假如你写了一个a < b ? a : b,也许你还想它可以或许处理惩罚表达式,它能实现吗?

那么,这里存在一些错误?不但有C++这样,在Java和C#中也如此,因为你知道min和max不是objects。

感激:

感激所有参加者,但愿各人的支持。

参考文献:

[1] http://aristeia.com/Papers/C++ReportColumns/jan95.pdf

[2] http://cuj.com/experts/1902/alexandr.html

[3] http://www.gotw.ca/gotw/077.htm

[4] A. Alexandrescu. "Traits: the else-if-then of Types," C++ Report, April 2000.

[5] A. Alexandrescu. Modern C++ Design (Addison-Wesley Longman, 2001).

[6] J. Vlissides and A. Alexandrescu. "To Code or Not to Code," C++ Report, March 2000.

[7] A. Alexandrescu. "Generic<Programming>: Mappings between Types and Values," C/C++ Users Journal Experts Forum, September 2000, http://www.cuj.com/experts/1810/alexandr.html.

[8] R. Kurzweil. The Age of Spiritual Machines: When Computers Exceed Human Intelligence (Penguin USA, 2000).

Andrei Alexandrescu is a Development Manager at RealNetworks Inc. (www.realnetworks.com), based in Seattle, WA, and author of the acclaimed book Modern C++ Design. He may be contacted at http://www.moderncppdesign.com/. Andrei is also one of the featured instructors of The C++ Seminar (www.gotw.ca/cpp_seminar).

译者:感激所以欣赏本文的人,但愿各人品评指教。

 

    关键字:

天才代写-代写联系方式