导出类指针可以被当成基类指针使用吗?

ylwy 2012-04-01 10:14:26
书上有云:“在通过指针或引用方式操作时,派生类对象可以当作基类对象看待和处理......用派生类指针给基类指针赋值时,不需要显示转换”。可是,如下代码:

class Base{
public:
void doIt(const char *){cout <<"Base do it";}
};
class Derived: public Base{
public:
void doIt(int){cout << "Deriver do it";}
};

template<class T,class X>
void TestDoit (X x,T p){
p->doIt(x);
}
int main(){
Derived * p = new Derived();
TestDoit ("abcdefg", p);
}

却不能在VS2008上通过编译。提示如下:

error C2664: “Derived::doIt”: 不能将参数 1 从“const char *”转换为“int”
没有使该转换得以执行的上下文
.\重载.cpp(20): 参见对正在编译的函数 模板 实例化“void TestDoit<Derived*,const char*>(X,T)”的引用
with
[
X=const char *,
T=Derived *
]


我不明白,为何不能通过指针“等价”去匹配“void TestDoit<Base*,int>(X,T)”,却一定要吊死在“void TestDoit<Derived*,const char*>(X,T)”这棵树上?!

请高手赐教!谢谢
...全文
1212 78 打赏 收藏 转发到动态 举报
写回复
用AI写文章
78 条回复
切换为时间正序
请发表友善的回复…
发表回复
NovalIDE 2012-04-13
  • 打赏
  • 举报
回复
void CSkinButton::SetAllImageSize( CSize sz )
{
m_allImageSize = sz;
m_allImage.m_Image.SetImageSize(m_allImageSize);
}

void CSkinButton::EnableFocus( BOOL bFoucs )
{
m_bDrawFocus = bFoucs;
}

void CSkinButton::SetImageLeft( int nLeft )
{
nImageLeft = nLeft;
}

void CSkinButton::SetImageTop( int nTop )
{
nImageTop = nTop;
}

void CSkinButton::SetTextLeft( int nLeft )
{
nTxtLeft = nLeft;
}

void CSkinButton::SetTextTop( int nTop )
{
nTxtTop = nTop;
}
//point.x左边距,point.y上边距
void CSkinButton::SetImagePosition( CPoint point )
{
SetImagePosition(point.x,point.y);
}

void CSkinButton::SetImagePosition( int x,int y )
{
SetImageLeft(x);
SetImageTop(y);
}

void CSkinButton::SetTextPosition( CPoint point )
{
SetTextPosition(point.x,point.y);
}

void CSkinButton::SetTextPosition( int x,int y )
{
SetTextLeft(x);
SetTextTop(y);
}

void CSkinButton::InitToolTip()
{
if (m_toolTip.GetSafeHwnd() == NULL){
m_toolTip.Create(this);
m_toolTip.Activate(FALSE);
}
}


void CSkinButton::SetTooltipText(LPCTSTR spText, BOOL bActivate)
{
EnableToolTip(TRUE);
InitToolTip();

if (m_toolTip.GetToolCount() == 0)
{
CRect rectBtn;
GetClientRect(rectBtn);
m_toolTip.AddTool(this, spText, rectBtn, 1);
}
m_toolTip.UpdateTipText((LPCTSTR)spText, this, 1);
m_toolTip.Activate(bActivate);
//m_toolTip.SetDelayTime(1);
}
BOOL CSkinButton::PreTranslateMessage(MSG* pMsg)
{
//是否显示工具提示
if (bShowTooltip){
//显示Tooltip提示
InitToolTip();
m_toolTip.RelayEvent(pMsg);
}
return CButton::PreTranslateMessage(pMsg);
}

void CSkinButton::EnableToolTip( BOOL bEnable )
{
if (bShowTooltip == bEnable){
return;
}
bShowTooltip = bEnable;
}
NovalIDE 2012-04-13
  • 打赏
  • 举报
回复
//选中禁止图片
BUTTON_IMAGE m_CheckedDisableImages;
BUTTON_STATE m_Style; //按钮形状(0-正常,1-当前,2-按下,3-锁定)
BOOL b_InRect; //鼠标进入标志
CString m_strText; //按钮文字
COLORREF m_TextForeColor; //文本颜色
COLORREF m_BackColor; //背景色
COLORREF m_LockForeColor; //锁定按钮的文字颜色
CRect m_ButRect; //按钮尺寸
CFont* p_Font; //字体

void DrawButton(CDC *pDC); //画正常的按钮
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
void SetTextFont(int FontHight,LPCTSTR FontName);
void SetBkColor(COLORREF color);
void SetTextForeColor(COLORREF color);
void SetText(CString str);
void DrawSkinButton( CDC * pDC );
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void PreSubclassWindow();
public:
afx_msg void OnSize(UINT nType, int cx, int cy);
void SetImageSize(CSize sz);

void LoadImage(LPCTSTR lpszResourceName, BUTTON_STATE Btn_State,CPoint stPoint);
void LoadImage(UINT uiResID, BUTTON_STATE Btn_State,CPoint stPoint);
void LoadImage(LPCTSTR lpszResourceName, BUTTON_STATE Btn_State,CRect stRect);
void LoadImage(UINT uiResID, BUTTON_STATE Btn_State,CRect stRect);
void LoadImageFromFile(LPCTSTR lpszFile, BUTTON_STATE Btn_State,CPoint stPoint);
void LoadImageFromFile(LPCTSTR lpszFile, BUTTON_STATE Btn_State,CRect stRect);
//设置透明遮蔽色
void SetTransparentColor( COLORREF clrTransparent );
BUTTON_IMAGE& GetButtonImage(BUTTON_STATE Btn_State);
//总显示,状态不变的背景图
BUTTON_IMAGE m_allImage;
void SetAllImageSize(CSize sz);
//设置是否显示焦点虚线框
void EnableFocus(BOOL bFoucs);
//设置图标左边距
void SetImageLeft(int nLeft);
//设置图标上边距
void SetImageTop(int nTop);
//设置图标左边距和上边距
void SetImagePosition(CPoint point);
//设置图标左边距和上边距
void SetImagePosition(int x,int y);
//设置文字左边距
void SetTextLeft(int nLeft);
//设置文字上边距
void SetTextTop(int nTop);
//设置文字左边距和上边距
void SetTextPosition(CPoint point);
//设置文字左边距和上边距
void SetTextPosition(int x,int y);
//创建工具提示控件
void InitToolTip();
//设置工具提示显示文本
void SetTooltipText(LPCTSTR spText, BOOL bActivate);
//禁止/启用工具提示
void EnableToolTip(BOOL bEnable);
protected:
//背景图标的尺寸
CSize m_allImageSize;
//状态图的尺寸
CSize m_ImageSize;
//背景图标左边距
int nImageLeft;
//背景图标上边距
int nImageTop;

//文本左边距
int nTxtLeft;
//文本上边距
int nTxtTop;

//是否画焦点虚线框
BOOL m_bDrawFocus;
//工具提示
CToolTipCtrl m_toolTip;
//是否显示工具提示
BOOL bShowTooltip;
public:
virtual BOOL PreTranslateMessage(MSG* pMsg);
};


NovalIDE 2012-04-13
  • 打赏
  • 举报
回复
#pragma once


// CSkinButton

#include "ControlDefine.h"
#include "XImage.h"

//DT_CENTER水平居中,DT_END_ELLIPSIS文本超过矩形区域是,截断字符串并用省略号结尾,DT_VCENTER垂直居中
const UINT uDefaultTextStyle = DT_SINGLELINE|DT_CENTER|DT_END_ELLIPSIS| DT_VCENTER ;

typedef struct _BUTTON_IMAGE
{
//按钮加载图片
CXImage m_Image;
//状态图在加载图片中的位置
CRect srcRect;
}BUTTON_IMAGE;


typedef struct _FONT_STYLE
{
//字体大小
UINT uSize;
//字体名称
CString strFont;
//字体前景色
COLORREF clrFore;
//字体背景色
COLORREF clrBack;
}FONT_STYLE;

typedef struct _TEXT_STATE
{
FONT_STYLE style;
BUTTON_STATE textState;
}TEXT_STATE;

class CSkinButton : public CButton
{
DECLARE_DYNAMIC(CSkinButton)

public:
CSkinButton();
virtual ~CSkinButton();

protected:
DECLARE_MESSAGE_MAP()
public:
virtual void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/);
//正常图片
BUTTON_IMAGE m_NormalImags;
//鼠标移过显示图片
BUTTON_IMAGE m_OverImages;
//鼠标按下显示图片
BUTTON_IMAGE m_DownImages;
//选中图片
BUTTON_IMAGE m_CheckedImags;
//选中后鼠标滑过显示图片
BUTTON_IMAGE m_CheckedOverImages;
//选中后鼠标按下显示图片
BUTTON_IMAGE m_CheckedDownImages;
//禁止图片
BUTTON_IMAGE m_DisableImages;
zhujin47 2012-04-11
  • 打赏
  • 举报
回复
模板是编译期进行推导类型的,const char*强转int 是运行时的行为,所以只能匹配继承类的。这两者的概念怎么能搞混呢?
NovalIDE 2012-04-11
  • 打赏
  • 举报
回复
#pragma once
#include "afxtoolbarimages.h"

class CXImage :
public CMFCToolBarImages
{
public:
CXImage(void);
~CXImage(void);
BOOL LoadFromFile(LPCTSTR lpszFile);
};
NovalIDE 2012-04-11
  • 打赏
  • 举报
回复
#include "StdAfx.h"
#include "XImage.h"
#include "FileAssist.h"
#include <..\src\mfc\afximpl.h>

CXImage::CXImage(void)
{
}

CXImage::~CXImage(void)
{
}

BOOL CXImage::LoadFromFile( LPCTSTR lpszFile )
{
CString strExt = FileAssist::GetFileExt(FileAssist::GetFileNameFromPath(lpszFile));
if (strExt.Compare("bmp") == 0){
return CMFCToolBarImages::Load(lpszFile);
}
else if (strExt.Compare("png") == 0){

if (m_bIsTemporary)
{
ASSERT(FALSE);
return FALSE;
}

ENSURE(lpszFile != NULL);

AfxDeleteObject((HGDIOBJ*)&m_hbmImageWell); // get rid of old one

CString strPath = lpszFile;

// Load images from the disk file:
UINT uiLoadImageFlags = LR_LOADFROMFILE | LR_CREATEDIBSECTION;
if (m_bMapTo3DColors)
{
uiLoadImageFlags |= LR_LOADMAP3DCOLORS;
}

HBITMAP hbmp = NULL;

// Try to load PNG image first:
CPngImage pngImage;
if (pngImage.LoadFromFile(strPath))
{
hbmp = (HBITMAP) pngImage.Detach();
}

if (hbmp == NULL)
{
TRACE(_T("Can't load png file: %s. GetLastError() = %x\n"), strPath, GetLastError());
return FALSE;
}

m_hbmImageWell = hbmp;

if (m_hbmImageWell == NULL)
{
TRACE(_T("Can't load bitmap: %s. GetLastError() = %x\r\n"), strPath, GetLastError());
return FALSE;
}

BITMAP bmp;
if (::GetObject(m_hbmImageWell, sizeof(BITMAP), &bmp) == 0)
{
ASSERT(FALSE);
::DeleteObject(m_hbmImageWell);
m_hbmImageWell = NULL;
return FALSE;
}

m_bUserImagesList = TRUE;
m_strUDLPath = strPath;

if (::GetFileAttributes(strPath) & FILE_ATTRIBUTE_READONLY)
{
m_bReadOnly = TRUE;
}

m_nBitsPerPixel = bmp.bmBitsPixel;
if (m_nBitsPerPixel > 8 && m_nBitsPerPixel < 32)
{
// LR_LOADMAP3DCOLORS don't support > 8bpp images, // we should convert it now:
MapTo3dColors(FALSE);
}

if (bmp.bmBitsPixel >= 32)
{
PreMultiplyAlpha(m_hbmImageWell);
}

UpdateCount();

AfxDeleteObject((HGDIOBJ*)&m_hbmImageLight);
m_hbmImageLight = NULL;

AfxDeleteObject((HGDIOBJ*)&m_hbmImageShadow);
m_hbmImageShadow = NULL;

return TRUE;

}
return CMFCToolBarImages::Load(lpszFile);
}
ylwy 2012-04-11
  • 打赏
  • 举报
回复
感谢大家。抱歉没有分可加了
gygood 2012-04-10
  • 打赏
  • 举报
回复
[Quote=引用 70 楼 的回复:]

不懂得都别在这误人子弟,还有人贴那么大堆模板,你们到底懂不懂问题出在哪啊?一句话就能说明白的事,你的子类覆盖了父类的方法,C++不同于Java,父类和子类的方法不能重载,因为作用域不同,结论,子类中没有const char*参数的那个方法,因为他被覆盖了
[/Quote]

70楼正解,该结贴了。
1. 问题不在帖子名称指的地方,代码中没用到多态。
2. 也就谈不上模板和多态。
allen95 2012-04-10
  • 打赏
  • 举报
回复
不懂得都别在这误人子弟,还有人贴那么大堆模板,你们到底懂不懂问题出在哪啊?一句话就能说明白的事,你的子类覆盖了父类的方法,C++不同于Java,父类和子类的方法不能重载,因为作用域不同,结论,子类中没有const char*参数的那个方法,因为他被覆盖了
haunying3 2012-04-07
  • 打赏
  • 举报
回复
后来,试了一下,不能在模版中使用RTTI
因为,模版只是编译时替换模版类型,不能进行选择性的编译部分代码。
即便判断出模版实例化的类型,也不能略过编译部分代码。
要是非要实现这个畸形功能,用模版的特化,或直接使用重载函数!
附上模版特化代码:
class Base{
public:
virtual void doIt(const char *){std::cout <<"Base do it";}
};
class Derived: public Base{
public:
//不是 虚函数:参数列表不同。即便加上virtual,也不能与virtual void doIt(const char *)构成统一动态绑定函数层次
void doIt(int){std::cout << "Deriver do it";}
};

template<class T,class X>
void TestDoit (X x,T p){
p->doIt(x);
}
//新增函数模版的特化
template<>
void TestDoit<Derived *,const char *>(const char * x,Derived * p){
p->Base::doIt(x);
}
//end of 新增函数模版的特化
int main(){
Derived * p = new Derived();
TestDoit ("abcdefg", p);
char ca[]="1234";
p->Base::doIt(ca);
TestDoit (10, p);
}
haunying3 2012-04-07
  • 打赏
  • 举报
回复
以前看过一个不相关的帖子,感觉37楼回答得很好,现在也是。
现在,我说一下这个问题吧(不知道楼主会不会看):
1、你这个不是多态,
<1> 多态要加上关键字virtual
<2>多态的函数的参数列表要完全相同
2、派生类中成员函数会屏蔽积累的同名成员函数
<1>你用派生类的指针去隐式访问基类的被屏蔽的成员是一个低级错误
<2>如果要访问基类被屏蔽的成员函数,要显式访问。
3、关于RTTI的在模版中的使用(下面的示例程序未包含),个人感觉不伦不类。(应该可以解决你的编译问
题但。。。。。。不是你写程序的问题的关键所在)
4、希望楼主加深对C++面向对象的语言特性的理解,以免再犯这些错误!
附上代码:
class Base{
public:
virtual void doIt(const char *){std::cout <<"Base do it";}
};
class Derived: public Base{
public:
//不是 虚函数:参数列表不同。即便加上virtual,也不能与virtual void doIt(const char *)构成统一动态绑定函数层次
void doIt(int){std::cout << "Deriver do it";}
};

template<class T,class X>
void TestDoit (X x,T p){
p->doIt(x);
}
int main(){
Derived * p = new Derived();
//TestDoit ("abcdefg", p); //
//正确的调用
TestDoit (10, p);
p->doIt(10);
//显式调用1
p->Base::doIt("abcdefg");
//显式调用2
static_cast<Base *>(p)->doIt("abcdefg");
//显式调用3
((Base *)p)->doIt("abcdefg");
//显式调用4
//虽然能通过把编译,但说什么好呢?(另外,这里省却了转换成功与否的判断)
dynamic_cast<Base *>(p)->doIt("abcdefg");
}
xmu_才盛 2012-04-07
  • 打赏
  • 举报
回复
TestDoit ("abcdefg", p); 这里面显示调用, 给出模板定型。

别把模板 的应用想的那么只能, 这不是高级语言
庄鱼 2012-04-05
  • 打赏
  • 举报
回复
[Quote=引用 59 楼 的回复:]

以具体类型替换 template parameters 的过程称为「具现化」(instantiation,或称「实体化」)。过程中会产生 template 的一份实体(instance)。
实际上,templates 会被编译两次:

1.不具现化,只是对 template 程序代码进行语法检查以发现诸如「缺少分号」等等的语法错误。
2.具现化时,编译程序检查 template 程序代码……
[/Quote]
真够辛苦的!
aQaQa 2012-04-05
  • 打赏
  • 举报
回复
是的,要先确定模板的具体类型(文章中说的具现化),然后再使用该类型(如调用该类型的类对象的函数)。
ylwy 2012-04-05
  • 打赏
  • 举报
回复
基本上明白了。很有收获,谢谢这里所有朋友的帮助
aQaQa 2012-04-05
  • 打赏
  • 举报
回复
以具体类型替换 template parameters 的过程称为「具现化」(instantiation,或称「实体化」)。过程中会产生 template 的一份实体(instance)。
实际上,templates 会被编译两次:

1.不具现化,只是对 template 程序代码进行语法检查以发现诸如「缺少分号」等等的语法错误。
2.具现化时,编译程序检查 template 程序代码中的所有调用是否合法,诸如「未获支持之函数调用」便会在这个阶段被检查出来。

2.2. 自变量推导(Argument Deduction)
当我们使用某一类型的自变量调用 max()时,template parameters 将以该自变量类型确定下来。如果我们针对参数类型 T const& 传递两个 ints,编译程序必然能够推导出 T 是 int。注意这里并不允许「自动类型转换」。是的,每个 T 都必须完全匹配其自变量。例如:

template <typename T>
inline T const& max(T const& a, T const& b);
...
max(4, 7); // OK,两个 T 都被推导为 int
max(4, 4.2); // 错误:第一个 T 被推导为 int,第二个 T 被推导为 double

有三种方法可以解决上述问题:
1. 把两个自变量转型为相同类型:
max(static_cast<double>(4), 4.2); // OK

2. 明确指定 T 的类型:
max<double>(4, 4.2); // OK

3. 对各个 template parameters 使用不同的类型(译注:意思是不要像上面那样都叫做 T)。下一节详细讨论这些问题。

2.3. Template Parameters(模板参数)
Function templates 有两种参数:
1. Template parameters(模板参数),在 function template 名称前的一对角(尖)括号中声明:
template <typename T> // T 是个 template parameter

2. Call parameters(调用参数),在 function template 名称后的小(圆)括号中声明:
... max (T const& a, T const& b); // a 和 b 是调用参数

template parameters 的数量可以任意,但你不能在 function templates 中为它们指定预设自变量值4(这一点与 class templates 不同)。例如你可以在 max()template 中定义两个不同类型的调用参数:

template <typename T1, typename T2>
inline T1 max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}
...
max(4, 4.2); // OK。回传类型和第一自变量类型相同

这似乎是一个可以为 max()template 的参数指定不同类型的好办法,但它也有不足。问题在于你必须声明传回值的类型。如果你使用了其中一个类型,另一个类型可能被转型为该类型。C++没有提供一个机制用以选择「效力更大的类型, the more powerful type」(然而你可以藉由某些巧妙的 template 编程手段来提供这种机制,参见 15.2.4 节, p.271)。因此,对于 42 和 66.66 两个调用自变量,max()的传回值要么是 double 66.66,要么是 int 66。另一个缺点是,把第二参数转型为第一参数的类型,会产生一个区域暂时对象(local temporary object),因而无法以 by reference 方式传回结果5。因此在本例之中,回传类型必须是 T1,不能是 T1 const&。

4 这个限制主要是由于 function templates 开发历史上碰到的小问题导致。对新一代 C++ 编译程序而言,这已经不再是问题了。将来这个特性也许会包含于 C++语言本身。参见 13.3 节, p.207。
5 你不能以 by reference 方式传出函数内的 local object,因为它一旦离开函数作用域,便不复存在。

由于 call parameters 的类型由 template parameters 建立,所以两者往往互相关联。我们把这种概念称为 function template argument deduction(函数模板自变量推导)。它使你可以像调用一个常规(意即 non-template)函数一样来调用 function template。

然而正如先前提到的那样,你也可以「明确指定类型」来具现化一个 template:

template <typename T>
inline T const& max (T const& a, T const& b);
...
max<double>(4,4.2); // 以 double 类型具现化 T

当 template parameters 和 call parameters 之间没有明显联系,而且编译程序无法推导出template parameters 时,你必须明确地在调用时指定 template arguments。例如你可以为 max()引入第三个 template argument type 作为回传类型:

template <typename T1, typename T2, typename RT>
inline RT max (T1 const& a, T2 const& b);

然而「自变量推导机制」并不对回传类型进行匹配6,而且上述的 RT 也并非函数调用参数(call parameters)中的一个;因此编译程序无法推导出 RT。你不得不像这样明确指出 template arguments:

template <typename T1, typename T2, typename RT>
inline RT max (T1 const& a, T2 const& b);
...
max<int,double,double>(4, 4.2);
// OK,但是相当冗长(译注:因为其实只需明写第三自变量类型,却连前两个自变量类型都得写出来)

以上我们所看到的是,要么所有 function template arguments 都可被推导出来,要么一个也推导不出来。另有一种作法是只明确写出第一自变量,剩下的留给编译程序去推导,你要做的只是把所有「无法被自动推导出来的自变量类型」写出来。因此,如果把上述例子中的参数顺序改变一下,调用时就可以只写明回传类型:

template <typename RT, typename T1, typename T2>
inline RT max (T1 const& a, T2 const& b);
...
max<double>(4,4.2); // OK,返回类型为 double

此例之中,我们调用 max()时,只明确指出回传类型 RT 为 double,至于 T1 和 T2 两个参数型别会被编译程序根据调用时的自变量推导为 int 和 double。

注意,这些 max()修改版本并没带来什么明显好处。在「单一参数」版本中,如果两个自变量的类型不同,你可以指定参数类型和回返值类型。总之,为尽量保持程序代码简单,使用「单一参数」的 max()是不错的主意。讨论其他 template 相关问题时,我们也会遵守这个原则。

自变量推导过程的细节将在第 11 章讨论。

6 推导过程也可以看作是重载决议机制(overload resolution)的一部份,两者都不倚赖回传值的类型来区分不同的调用。惟一的例外是:转型运算符成员函数(conversion operator members)倚赖回传型别来进行重载决议(overload resolution)。(译注:「转型运算符」函数名称形式如下:operator type(),其中的 type 可为任意类型;无需另外指出回传类型,因为函数名称已经表现出回传类型。)

2.4. 重载(Overloading)Function Templates
就像常规(意即 non-template)functions 一样,function templates 也可以被重载(译注:C++标准链接库中的许多 STL 算法都是如此)。这就是说,你可以写出多个不同的函数定义,并使用相同的函数名称;当客户调用其中某个函数时,C++编译程序必须判断应该唤起哪一个函数。即使不牵扯 templates,这个推断过程也非常复杂。本节讨论的是,一旦涉及 templates,重载将是一个怎样的过程。如果你对 non-templates 情况下的重载机制还不太清楚,可以先参考附录 B,那里我们对重载机制做了相当深入的讲解。

下面这个小程序展示如何重载一个 function template:
// basics/max2.cpp

// 传回两个 ints 中的较大者
inline int const& max (int const& a, int const& b)
{
return a < b ? b : a;
}

// 传回两任意类型的数值中的较大者
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}

// 传回三个任意类型值中的最大者
template <typename T>
inline T const& max (T const& a, T const& b, T const& c)
{
return ::max (::max(a,b), c);
}

int main()
{
::max(7, 42, 68); // 唤起「接受三个自变量」的函数
::max(7.0, 42.0); // 唤起 max<double>(经由自变量推导)
::max('a', 'b'); // 唤起 max<char>(经由自变量推导)
::max(7, 42); // 唤起「接受两个 int 自变量」的 non-template 函数
::max<>(7, 42); // 唤起 max<int>(经由自变量推导)
::max<double>(7, 42); // 唤起 max<double>(无需自变量推导)
::max('a', 42.7); // 唤起「接受两个 int 自变量」的 non-template 函数
}
/* 译注:ICL7.1/g++ 3.2 顺利通过本例。VC6 无法把最后一个调用匹配到常规的(non-template)函数 max(),造成编译失败。VC7.1 可顺利编译,但对倒数第二个调用给出警告:虽然它唤起的是 function template max(),但它发现常规函数 max()与这个调用更匹配。*/

这个例子说明:non-template function 可以和同名的 function template 共存,也可以和其相同类型的具现体共存。当其他要素都相等时,重载决议机制会优先选择 non-template function,而不选择由 function template 具现化后的函数实体。上述第四个调用便是遵守这条规则:

::max(7, 42); // 两个自变量都是 int,吻合对应的 non-template function

但是如果可由 template 产生更佳匹配,则 template 具现体会被编译程序选中。前述的第二和第三个调用说明了这一点:

::max(7.0, 42.0); // 唤起 max<double>(经由自变量推导)
::max('a', 'b'); // 唤起 max<char>(经由自变量推导)

调用端也可以使用空的 template argument list,这种形式告诉编译程序「只从 template 具现体中挑选适当的调用对象」,所有 template parameters 都自 call parameters 推导而得:

::max<>(7, 42); // 唤起 max<int>(经由自变量推导)

另外,「自动类型转换」只适用于常规函数,在 templates 中不予考虑,因此前述最后一个调用唤起的是 non-template函数。在该处,'a' 和 42.7 都被转型为 int:

::max('a', 42.7); // 本例中只有 non-template 函数才可以接受两个不同类型的自变量

下面是一个更有用的例子,为指针类型和 C-style 字符串类型重载了 max()template:
// basics/max3.cpp

#include <iostream>
#include <cstring>
#include <string>

// 传回两个任意类型值的较大者
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}

// 传回两个指针的较大者(所谓较大是指「指针所指之物」较大)
template <typename T>
inline T* const& max (T* const& a, T* const& b)
{
return *a < *b ? b : a;
}

// 传回两个 C-style 字符串的较大者(译注:C-style 字符串必须自行定义何谓「较大」)。
inline char const* const& max (char const* const& a, char const* const& b)
{
return std::strcmp(a,b) < 0 ? b : a;
}

int main()
{
int a=7;
int b=42;
::max(a,b); // 唤起「接受两个 int」的 max()

std::string s = "hey";
std::string t = "you";
::max(s,t); // 唤起「接受两个 std::string」的 max()

int *p1 = &b;
int *p2 = &a;
::max(p1,p2); // 唤起「接受两个指针」的 max()

char const* s1 = "David";
char const* s2 = "Nico";
::max(s1, s2); // 唤起「接受两个 C-style 字符串」的 max()
}

注意,所有重载函数都使用 by reference 方式来传递自变量。一般说来,不同的重载形式之间最好只存在「绝对必要的差异」。各重载形式之间应该只存在「参数个数的不同」或「参数类型的明确不同」,否则可能引发各种副作用。举个例子,如果你以一个「by value 形式的 max()」重载一个「by reference 形式的 max()」(译注:两者之间的差异不够明显),就无法使用「三自变量」版本的 max()来取得「三个 C-style 字符串中的最大者」:
// basics/max3a.cpp

#include <iostream>
#include <cstring>
#include <string>

// 传回两个任意类型值的较大者(call-by-reference)
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}
// 传回两个 C-style 字符串的较大者(call-by-value)
inline char const* max (char const* a, char const* b)
{
return std::strcmp(a,b) < 0 ? b : a;
}

// 传回三个任意类型值的最大者(call-by-reference)
template <typename T>
inline T const& max (T const& a, T const& b, T const& c)
{
return ::max (::max(a,b), c); // 当 max(a,b)采用 by value 形式时,此行错误
}

int main()
{
::max(7, 42, 68); // OK

const char* s1 = "frederic";
const char* s2 = "anica";
const char* s3 = "lucas";
::max(s1, s2, s3); // ERROR
}

本例中针对三个 C-style 字符串调用 max(),会出现问题。以下这行述句是错误的:

return ::max (::max(a,b), c);

因为 C-style 字符串的 max(a,b) 重载函数创建了一个新而暂时的区域值(a new, temporary local value),而该值却以 by reference 方式被传回(那当然会造成错误)。

这只是细微的重载规则所引发的非预期行为例子之一。当函数调用动作发生时,如果不是所有重载形式都在当前范围内可见,那么上述错误可能发生,也可能不发生。事实上,如果把「三自变量」版本的 max()写在接受两个 ints 的 max()前面(于是后者对前者而言不可见),那么在调用「三自变量」max()时,会间接唤起「双自变量」max() function template:

// basics/max4.cpp

// 传回两个任意类型值的较大者
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}

// 传回三个任意类型值的最大者
template <typename T>
inline T const& max (T const& a, T const& b, T const& c)
{
return ::max (::max(a,b), c);
// 即使自变量类型都是 int,
// 这里也会调用 max()template。因为下面的函数定义来得太迟。
}

// 传回两个 ints 的较大者
inline int const& max (int const& a, int const& b)
{
return a < b ? b : a;
}

9.2 节, p.121 详细讨论这个问题。就目前而言,你应该遵循一条准则:总是把所有形式的重载函式写在它们被调用之前。
2.5. 摘要
 Function templates 可以针对不同的 template arguments 定义一整族(a family of)函数。
 Function templates 将依照传递而来的自变量(arguments)的类型而被具现化(instantiated)。
 你可以明确指出 template parameters。
 Function templates 可以被重载(overloaded)。
 重载 function templates 时,不同的重载形式之间最好只存在「绝对必要的差异」。
 请确保所有形式的重载函数都被写在它们的被调用点之前。

ylwy 2012-04-05
  • 打赏
  • 举报
回复
[Quote=引用 52 楼 的回复:]
vs2008没任何问题。你可以先清理然后rebuild
[/Quote]

真是对不起,你的代码确实是可以通过编译的,是我输入时有误,着急了。

谢谢
pengzhixi 2012-04-05
  • 打赏
  • 举报
回复
编译器不可能根据p->doIt()来反推p的类型,不可能这么做。因为p必须说是类型先确定之后p->doIt才有意义。
pengzhixi 2012-04-05
  • 打赏
  • 举报
回复
不是推出 Derived::doIt(); 只是推出你传递的参数类型是Derived*
而对于p->doIt();是推导参数类型以后的事情了。这个就属于实例化的语法检查了。
ylwy 2012-04-05
  • 打赏
  • 举报
回复
[Quote=引用 48 楼 的回复:]
请参靠侯捷的《C++ template 全览》,我这有简体中文的前5节内容,相信你看完就明白了,如需要,可以留邮箱给我。
[/Quote]

谢谢你的热心,C++的书我倒是应有尽有
加载更多回复(58)

64,683

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧