看到一段关于C++模板的tricky code,有点疑惑。求教。

nossiac 2012-11-21 09:48:16
原文在这里 http://blog.csdn.net/orbit/article/details/4317#atltemplates
不长,就不搬了。

我有两个疑问:
1、作者说,用这种指针转换可以免去虚函数,获得性能提升。这跟虚函数怎么扯上关系的啊?这样的效果,不就是常见的子类覆盖基类方法吗?折腾这一圈意图倒底是什么呢?
2、作者的代码里,sayhi被声明为protected,G++会报错,这什么情况?


代码大概是这样。

#include <iostream>

using namespace std;

template <typename T>
class Base
{
public:
void hi()
{
T * pT = static_cast<T*>(this);
pT->sayhi();
}
//protected:
void sayhi()
{
cout<<"hi i am base class"<<endl;
}
};

class A : public Base<A>
{
public:
//protected:
void sayhi()
{
cout<<"hi i am A class"<<endl;
}
};

class B : public Base<B>
{
};

main()
{
A a;
B b;

a.hi(); // prints "hi i am A class"
b.hi(); // prints "hi i am B class"
}
...全文
297 14 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
mLee79 2012-11-21
  • 打赏
  • 举报
回复
引用 13 楼 nossiac 的回复:
引用 6 楼 mLee79 的回复:史前时期的ATL/WTL一直都是这样子写地. 我这正看WTL呢。 8.0不是最新版么,怎么还成史前时代了啊。 新的code难道有新写法了么……
我工作的前几年一直写这样的代码, 这一晃都10多年了, 不是史前的东西就怪了...
nossiac 2012-11-21
  • 打赏
  • 举报
回复
引用 6 楼 mLee79 的回复:
史前时期的ATL/WTL一直都是这样子写地.
我这正看WTL呢。 8.0不是最新版么,怎么还成史前时代了啊。 新的code难道有新写法了么……
nossiac 2012-11-21
  • 打赏
  • 举报
回复
引用 5 楼 firendlys 的回复:
其实,模板是在编译器代码就已经完全展开了,也就是说,严格来说,模板的最终效果与所有编译器确定的效果(比如类函数的覆盖)是完全没有区别的..... 换句话说,这种写法带来的唯一好处就是可以减少代码的编写量(注:纯属个人认为)... (其实你用一个模板的时候,不也是这个目的吗?同一类型的代码不想重复多写几次,那就用模板吧,让代码由编译器自动生成,而你自己,则只写一次即……
非常感谢。看完你的例子,我才想到了这个trick的应用场景。 作者的例子太粗糙了,没有把背后的玄机讲出来。所以我一直纳闷作者这么折腾有什么意思…… 你举的这个例子非常好啊!哈哈。
wenhong609 2012-11-21
  • 打赏
  • 举报
回复
引用 8 楼 nossiac 的回复:
引用 2 楼 wenhong609 的回复:好吧,我先来下吧。这个比较狗血,第一次遇到。首先是这一句: T * pT = static_cast<T*>(this);this的类型是Base<T>*,将父类的类型转换为子类,这好像不是很好吧,但实际上是行的。和虚函数扯上关系这里和虚函数真的很像,虚函数是动态绑定,所以效率不如这里的静态绑定,和虚函数的效果是一样的。其实只……
差不多吧,不是虚函数,但是和虚函数很像。但是说到效率,还真不好说,因为这种方式会涉及到函数栈的问题,多态是动态绑定,到底谁的效率高,只有实验才知道了。
wenhong609 2012-11-21
  • 打赏
  • 举报
回复
引用 9 楼 oniisama 的回复:
C/C++ code?12345A a; B b; a.hi(); // prints "hi i am A class" b.hi(); // prints "hi i am B class" 写这种代码有什么用?难道这样的代码叫多态?
多态是动态绑定,前提得有虚函数和继承,这里当然不是多台。
oniisama 2012-11-21
  • 打赏
  • 举报
回复
A a;
    B b;
  
    a.hi();    // prints "hi i am A class"
    b.hi();    // prints "hi i am B class"
写这种代码有什么用?难道这样的代码叫多态?
nossiac 2012-11-21
  • 打赏
  • 举报
回复
引用 2 楼 wenhong609 的回复:
好吧,我先来下吧。这个比较狗血,第一次遇到。首先是这一句: T * pT = static_cast<T*>(this);this的类型是Base<T>*,将父类的类型转换为子类,这好像不是很好吧,但实际上是行的。和虚函数扯上关系这里和虚函数真的很像,虚函数是动态绑定,所以效率不如这里的静态绑定,和虚函数的效果是一样的。其实只是和Base *b=new Derived(……
嗯,我大概明白作者想表达什么了。 这是在编译器期模拟虚函数调用,只是作者写的实例没有体现出虚函数。 按这个tricky,基类指针指向子类对象,就能调用子类成员函数,在编译器达到类似虚函数效果。不过,貌似也有缺陷,当基类指针指向基类对象时,成员函数也被子类覆盖了。
firendlys 2012-11-21
  • 打赏
  • 举报
回复
附:虚函数的实现方式(这也是你题目提到的,和虚函数有什么关系),方便你自己比较三种情况:

class Base
{
public:
	//基类函数写一次:
	void doWork()
    {
        do1();
		do2();
		do3();
    }
	//三个具体执行任务的函数都变成虚函数
	virtual void do1(){cout<<"Base Do1;"};
	virtual void do2(){cout<<"Base Do2;"};
	virtual void do3(){cout<<"Base Do3;"};
};
 
class A : public Base
{
public:
	//同样不需要重写 doWork()
	void do1(){cout<<"A Do1;"};
	void do3(){cout<<"A Do3;"};
};
 
class B :public Base
{
	void do2(){cout<<"B Do2;"};
	void do3(){cout<<"B Do3;"};
};

main()
{
    A a;
    B b;
    a.doWork();//假如是这样使用的话,和模板方式/继承方式没有任何区别. 
    b.doWork(); //最大的问题也就是虚函数表带来的开销了..

	//另外,虚函数的方式还可以实现基类指针调用派生类函数,
	// 这也是虚函数真正存在的理由,因为这个无法在编译器决定,
	//也就是模板和纯粹继承的方式是实现不了的..
	Base *base=new A();
	b->doWork();//与 a.doWork() 一样.
	//如果一定要用到这种方式,那就只能使用虚函数.

	//但是如果不需要用到基类指针调用派生类函数,那就有3种方式可选,
	//即上面提到的 普通继承, 模板 , 虚函数
	//三者各有优缺点,看个人需要吧...
}
--------- 总结一句: 这种模板的使用方式,其实就是所谓的"编译器多态",即,在编译期间可以让基类调用派生类的函数!!(而不是像虚函数那样在运行期调用!)
mLee79 2012-11-21
  • 打赏
  • 举报
回复
史前时期的ATL/WTL一直都是这样子写地.
firendlys 2012-11-21
  • 打赏
  • 举报
回复
其实,模板是在编译器代码就已经完全展开了,也就是说,严格来说,模板的最终效果与所有编译器确定的效果(比如类函数的覆盖)是完全没有区别的..... 换句话说,这种写法带来的唯一好处就是可以减少代码的编写量(注:纯属个人认为)... (其实你用一个模板的时候,不也是这个目的吗?同一类型的代码不想重复多写几次,那就用模板吧,让代码由编译器自动生成,而你自己,则只写一次即可...) 所以,这里模板的这种用途,其实也是一样的目的:减少你代码的编写量. 简单来说,就是不需要你在子类里面重写那个" void hi() "的函数... 如果你不采用这种方式的话,你就必须在每个派生类里面都重写那个 void hi() 函数,而关键的是,那个 void hi() 函数的代码在每个派生类(包括基类)里面是完全一样的! 用了这种方法之后,你只需要在基类里面写一次这个代码,而在派生类里面不需要再写这个hi()的代码了.. 举个例子吧:

template <typename T>
class Base
{
public:
  // 假如一个工作有do1,do2,do3 这3个工序,但是不同的类型的具体做法不同...
  // 不过类 Base,A,B对这3道工序的详细情况有些不同...
  // 在模板的情况下,你只需要在基类里面写一次:
    void doWork()
    {
        T * pT = static_cast<T*>(this);
        pT->do1();
		pT->do2();
		pT->do3();
    }
	void do1(){cout<<"Base Do1;"};
	void do2(){cout<<"Base Do2;"};
	void do3(){cout<<"Base Do3;"};
};
 
class A : public Base<A>
{
public:
   // 派生类里面不需要再重复写这个 dowork() 函数了...
  // 只需要把对应与Base()的不同的那道工序写出来就可以了..
	void do1(){cout<<"A Do1;"};
	void do3(){cout<<"A Do3;"};
};
// 实际使用的时候: A a;a.dowork() 会依次调用 A::do1() , Base::do2() , A::do3();
 
//..
class B : public Base<B>
{
	void do3(){cout<<"B Do3;"};
};

但是,如果没有采用模板(也没有采用虚函数),你要实现这种方式,必须为A,B类重写那个dowork()函数

class Base
{
public:
	void doWork()
    {
        do1();
		do2();
		do3();
    }
	void do1(){cout<<"Base Do1;"};
	void do2(){cout<<"Base Do2;"};
	void do3(){cout<<"Base Do3;"};
};
 
class A : public Base<A>
{
public:
	void doWork()
    {
        do1();
		do2();
		do3();
    }
		void do1(){cout<<"A Do1;"};
	void do3(){cout<<"A Do3;"};
};
 
class B : public Base<B>
{
	void doWork()
    {
        do1();
		do2();
		do3();
    }
	void do2(){cout<<"B Do2;"};
	void do3(){cout<<"B Do3;"};
};

然后,假设你有了很多这种派生类,然后,发现工序突然变了,变成 do2(),do3(),do1() ,然后,所有派生类你就自己一个一个慢慢修改吧... 如果采用这种模板机制,你只需要修改基类的一个函数就可以了... (当然,也可以采用虚函数的方式代替) 另外,虚函数的出现,不就是解决这类问题的吗? 由基类决定函数工序顺序,由派生类来具体的执行任务... 而这种模板机制,只不过把虚函数的这个功能由运行期提前到编译器决定,减少了虚函数的调用开销罢了... 大概就这么多了,其他就由楼上楼下补充了....
prajna 2012-11-21
  • 打赏
  • 举报
回复
假如有: class Base *pB class A *pA 我的理解 pB->hi()應該比pA->hi() 來得快。 如果真的是這樣, 這種方法也能想得出來,這世界, 什麽牛人都有啊。
prajna 2012-11-21
  • 打赏
  • 举报
回复
應該是繞過c++的多態機制,直接強制轉化。
wenhong609 2012-11-21
  • 打赏
  • 举报
回复
好吧,我先来下吧。这个比较狗血,第一次遇到。首先是这一句: T * pT = static_cast<T*>(this);this的类型是Base<T>*,将父类的类型转换为子类,这好像不是很好吧,但实际上是行的。和虚函数扯上关系这里和虚函数真的很像,虚函数是动态绑定,所以效率不如这里的静态绑定,和虚函数的效果是一样的。其实只是和Base *b=new Derived()的效果是一样的,这也是最常用的,但是如果要实现Base *b=new Based()这里就不行了,应为这里的静态绑定只实现了前一种动态绑定,当然前提是类Base里面有虚函数。

65,187

社区成员

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

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