导出类指针可以被当成基类指针使用吗?
书上有云:“在通过指针或引用方式操作时,派生类对象可以当作基类对象看待和处理......用派生类指针给基类指针赋值时,不需要显示转换”。可是,如下代码:
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)”这棵树上?!
请高手赐教!谢谢
[解决办法]
Base * p = new Derived();
[解决办法]
函数模板在编译时做匹配,你传入的参数是什么类型就生成什么代码。
除非你显示的告诉编译期你传入的类型
TestDoit <Base*, const char*>("abcdefg", p); 这样p就会转换成基类指针
[解决办法]
楼主,多了解一下 类成员函数的重载、覆盖、隐藏这方面的东东
[解决办法]
[解决办法]
[解决办法]
不是你用词不规范,而是你真的还没理解。
[解决办法]
谁告诉你推导的就是Base*呢?即使你将派生类的doIt删掉 那推导出来的也是Derived*。这并不影响里面的p->doIt的调用。只是说派生类将基类的这个函数接口以及实现都继承了。
[解决办法]
模板推导过程中除了数组指针弱化,以及非const到const引用转换外,是不会发生发生其他隐式转换的,你这里传的是派生类的指针,推导出来就是派生类的,是不会转换为基类指针
另外,你这里不是什么多态,只是一个隐藏的问题
[解决办法]
[解决办法]
添加 useing doit(char *);
[解决办法]
[解决办法]
- C/C++ code
class Derived;class Base{ friend void TestDoit (Derived*); // 友元 void doIt(const char*){cout <<"Base do it";}};class Derived: public Base{};void TestDoit (Derived*p){ p->doIt("hello");}int main(){ Derived * p = new Derived(); TestDoit (p); system("pause"); return 0;}
------解决方案--------------------
你TestDoit声明为友元当然可以访问了。
[解决办法]
[解决办法]
不是推出 Derived::doIt(); 只是推出你传递的参数类型是Derived*
而对于p->doIt();是推导参数类型以后的事情了。这个就属于实例化的语法检查了。
[解决办法]
以具体类型替换 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 时,不同的重载形式之间最好只存在「绝对必要的差异」。
?请确保所有形式的重载函数都被写在它们的被调用点之前。
[解决办法]
这里子类把父类的函数隐藏了,并不是多态啊,自然只能匹配子类的函数
如果new完再转成父类的指针试试,就应该可以了
Derived * p = new Derived();
Base *pBase = static_cast<Base*>(p);
TestDoit ("abcdefg", pBase);
[解决办法]
Derived * p1 = new Derived();
p1->doIt("22222");
你看一下这句能否编译通过就知道了。
[解决办法]
因为编译器知道p是Derived class的指针,它当然调用p的doit()
[解决办法]
后来,试了一下,不能在模版中使用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);
}