原题如下:
试参考以下代码,完成一个static_log2,该模板类的功能是求log2(int n)的整数部分

template <int n>
class static_fact //求n的阶乘
{
public: 
      enum { value = static_fact<n-1>::value * n };
};

template <>
class static_fact<0>

 public:enum { value = 1 };
};

#include <stdio.h>
void main()
{     
printf(“3! = %d “, static_fact<3>::value);
}

 没研究过template,连c++也不是太熟,不过看了一会,大概看出是个递归,剩下的就是想递归的结束条件了, 可怜我脑袋不太灵光,刚开始想歪了,浪费了不少时间,还好,后来还是想出来了,//blush

  还有一题就是一个比较难的竞赛题了, 我的数学不好,没搞出来,哪天有空,搜一下看看怎么做的:)

  本内容接上题,上题的写法是写出一个求出logk(int n)的整数部分的template,结果如下:

#include <iostream>
using namespace std;
template <int n>
class static_log2N
{
    public:
        enum {value = static_log2N<n/2>::value+1 };
}    ;

template <>
class static_log2N<1>
{
    public:
        enum{value = 0};
};

int main()
{
    cout<<static_log2N<28>::value<<endl;
    cin.get();
}       

进一步扩展,可以写出一个求出底为k,求log(int k)(int N)的整数部分的程序来,如下:

#include <iostream>
using namespace std;
template <int base,int n>
class static_logxN
{
    public:
        enum {value = static_logxN<base,n/base>::value+1 };
}    ;

template <int base>
class static_logxN<base,1>
{
    public:
        enum{value = 0};
};

int main()
{
    cout<<static_logxN<3,28>::value<<endl;
    cin.get();
}       

   昨天去书店找关于此类编程的资料,在c++ templates一书中找到称其为metaprogamming, 呵呵,孤陋寡闻了, 这本书的中文版太贵了,而且网上评论说译的不是太好,于是上午就买了本影印版,看上去是有点费劲,不过可省了近20块大洋呢^_^  趁这几天有时间,抓紧看一下:)

June 19th, 2007Covariant return types

Covariant return types–翻译成中文的字面意思应该是协变返回类型, 它的意思可以概括为在面向对象的编程中, 子类在重写父类的方法时可以改变这个方法的返回值的类型. 一般来讲子类在重写(override)父类方法时, 要求这个方法在参数列表和返回值上都要和父类中的对应的方法保持相同的类型. 所以下面的代码是无法通过编译的:

class Base
{
public:
virtual int foo () {return 0;}
};

class Child : public Base
{
virtual long foo () {return 1;} //compile error, 'Child::foo': overridin
//virtual function return type differs
//and is not covariant from 'Base::foo'
};

也许大家还没有看到这个限制的不爽之处, 那么设想如下的情形, 你想为你的父类和子类都写一个clone ()方法, 返回一个clone出的对象的指针, 如果没有Covariant return type, 你需要写类似如下代码:

class Base
{
public:
Base* clone ();
};
class Child : public Base
{
public:
Base* clone ();
};

Child* bp = static_cast<child*>(child.clone ());</child*>

这里每个子类的clone方法都返回一个指向Base的指针, 如果想要转换成子类的指针还需要一次额外的类型转换, (当然也许你认为使用static_cast会在编译期间完成, 不会影响运行效率, 但显然代码不够优雅:) 如果使用Covariant return type, 我们就可以把每个子类的clone方法返回一个指向子类对象的指针类型了:

class Base
{
public:
Base* clone ();
};
class Child : public Base
{
public:
Child* clone ();
};
Child* bp = child.clone (); //no extra type cast needed.

实际上Covariant return type就是用来这种在子类重写父类方法, 并且它的返回值的类型和父类方法的返回类型具有一定的相关性(一般是其衍生类型即派生类). 在C++标准03版的10.3.5中对其covaraiant return type做了详细的描述. 大家有兴趣可以去翻一下.

March 25th, 2007cv-qualified type

常在bbs上看人讲cv-qualified type, 一直没搞明白这是个嘛东西, 今天google了一下, 居然没有找到答案, 于是想起google group,果然在里面搜到了, 这里有一个人问了个什么是cv-qualified type, 看了一下, 原来cv分别是const和voliate的简写…

  a cv-qualified type is the “const”, “volatile” or “const volatile”
version of a type. that is, a variable, function, etc. declared without
one of those is cv-unqualified, while the addition of one of those
specifies a cv-qualified type.

  翻开标准还真有这么一节专门讲它的…

  保留着,以后兴许能用到:)

  from http://dev.csdn.net/article/39/39757.shtm

   MFC功能已经非常强大,自己做界面库也许没什么意思,但是这个过程中却能学到很多东西。比如说:

  窗口类的封装,从全局窗口消息处理到窗口对象消息处理的映射方法:

  对界面进行封装,一般都是一个窗口一个类,比如实现一个最基本的窗口类CMyWnd,你一定会把窗口过程作为这个类的成员函数,但是使用WINAPI创建窗口时必须注册类WNDCLASS,里面有个成员数据lpfnWndProc需要WNDPROC的函数指针,一般想法就是把窗口类的消息处理函数指针传过去,但是类成员函数除非是静态的,否则无法转换到WNDPROC,而全局的消息处理函数又无法得到窗口类对象的指针。

这里有几种解决办法:

    一种解决方法是用窗口列表,开一个结构数组,窗口类对象创建窗口的时候把窗口HWND和this指针放入数组,全局消息处理函数遍历数组,利用HWND找出this指针,然后定位到对象内部的消息处理函数。这种方法查找对象的时间会随着窗口个数的增多而增长。

    另一种方法比较聪明一点,WNDCLASS里面有个成员数据cbWndExtra一般是不用的,利用这点,注册类时给该成员数据赋值,这样窗口创建时系统会根据该值开辟一块内存与窗口绑定,这时把创建的窗口类的指针放到该块内存,那么在静态的窗口消息循环函数就能利用GetWindowLong(hWnd,GWL_USERDATA)取出该指针,return (CMyWnd*)->WindowProc(…),这样就不用遍历窗口了。但是这样一来就有个致命弱点,对窗口不能调用SetWindowLong(hWnd,GWL_USERDATA,数据),否则就会导致程序崩溃。幸好这个函数(特定这几个参数)是调用几率极低的,对于窗口,由于创建窗口都是调用窗口类的Create函数,不用手工注册WNDCLASS类,也就不会调用SetWindowLong函数。但是毕竟缺乏安全性,而且当一秒钟内处理的窗口消息很多时,这种查找速度也可能不够快。

    还有一种就是比较完美的解决办法,称之为thunk技术。thunk是一组动态生成的ASM指令,它记录了窗口类对象的this指针,并且这组指令可以当作函数,既也可以是窗口过程来使用。thunk先把窗口对象this指针记录下来,然后转向到静态stdProc回调函数,转向之前先记录HWND,然后把堆栈里HWND的内容替换为this指针,这样在stdProc里就可以从HWND取回对象指针,定位到WindowProc了。

    我们先来看看窗口过程函数定义:
   
    LRESULT WINAPI WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
    
    其实当我们的窗口类CMyWnd创建窗口的时候,窗口句柄是可以得到并且作为成员数据保存,如此一来,第一个参数hWnd是可以不要的,因为可以通过this->m_hWnd得到,我们可以在这里做手脚,hWnd其实质是一个指针,如果把这个参数替换为窗口类对象的this指针,那么我们不就可以通过(CMyWnd*)hWnd->WindowProc转到窗口类内部的窗口过程了吗?但是窗口过程是系统调用的,怎么能把hWnd替换掉呢?我们先来看看系统调用这个函数时的堆栈情况:

系统调用m_thunk时的堆栈:
ret HWND MSG WPARAM LPARAM
——————————————-
栈顶                                              栈底

系统把参数从右到左依次压栈,最后把返回地址压栈,我们只要在系统调用窗口过程时修改堆栈,把其中的hWnd参数替换掉就行了。这时thunk技术就有用武之地了,我们先定义一个结构:

#pragma pack(push,1) //该结构必须以字节对齐
struct Thunk {
 BYTE    Call;
 int    Offset;
 WNDPROC   Proc;
 BYTE    Code[5];
 CMyWnd*   Window;
 BYTE    Jmp;
 BYTE    ECX; 
};
#pragma pack(pop)

类定义:
class CMyWnd
{
public:
   BOOL Create(…);
   LRESULT WINAPI WindowProc(UINT,WPARAM,LPARAM);
   static LRESULT WINAPI InitProc(HWND,UINT,WPARAM,LPARAM);
   static LRESULT WINAPI stdProc(HWND,UINT,WPARAM,LPARAM);
   WNDPROC CreateThunk();
   WNDPROC GetThunk(){return m_thunk}
   …

private:
   WNDPROC m_thunk
}

在创建窗口的时候把窗口过程设定为this->m_thunk,m_thunk的类型是WNDPROC,因此是完全合法的,当然这个m_thunk还没有初始化,在创建窗口前必须初始化:

WNDPROC CMyWnd::CreateThunk()
{
   Thunk*  thunk = new Thunk;
 
 ///////////////////////////////////////////////
 //
 //系统调用m_thunk时的堆栈:
 //ret HWND MSG WPARAM LPARAM
 //——————————————-
 //栈顶                                             栈底
 ///////////////////////////////////////////////

 //call Offset
 //调用code[0],call执行时会把下一条指令压栈,即把Proc压栈
 thunk->Call = 0xE8;        // call [rel]32
 thunk->Offset = (size_t)&(((Thunk*)0)->Code)-(size_t)&(((Thunk*)0)->Proc);  // 偏移量,跳过Proc到Code[0]
 thunk->Proc = CMyWnd::stdProc;  //静态窗口过程
 
 //pop ecx,Proc已压栈,弹出Proc到ecx 
 thunk->Code[0] = 0x59;  //pop ecx
 
 //mov dword ptr [esp+0x4],this
 //Proc已弹出,栈顶是返回地址,紧接着就是HWND了。
 //[esp+0x4]就是HWND
 thunk->Code[1] = 0xC7;  // mov
 thunk->Code[2] = 0x44;  // dword ptr
 thunk->Code[3] = 0x24;  // disp8[esp]
 thunk->Code[4] = 0x04;  // +4
 thunk->Window = this;
 
 //偷梁换柱成功!跳转到Proc
 //jmp [ecx]
 thunk->Jmp = 0xFF;     // jmp [r/m]32
 thunk->ECX = 0x21;     // [ecx]
 
 m_thunk = (WNDPROC)thunk;
 return m_thunk;
}

这样m_thunk虽然是一个结构,但其数据是一段可执行的代码,而其类型又是WNDPROC,系统就会忠实地按窗口过程规则调用这段代码,m_thunk就把Window字段里记录的this指针替换掉堆栈中的hWnd参数,然后跳转到静态的stdProc:

//本回调函数的HWND调用之前已由m_thunk替换为对象指针
LRESULT WINAPI CMyWnd::stdProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{
 CMyWnd* w = (CMyWnd*)hWnd;
 
  return w->WindowProc(uMsg,wParam,lParam);
}

这样就把窗口过程转向到了类成员函数WindowProc,当然这样还有一个问题,就是窗口句柄hWnd还没来得及记录,因此一开始的窗口过程应该先定位到静态的InitProc,CreateWindow的时候给最后一个参数,即初始化参数赋值为this指针:

CreateWindowEx(
        dwExStyle,
                  szClass,
                  szTitle,
                  dwStyle,
                  x,
                  y,
                  width,
                  height,
                  hParentWnd,
                  hMenu,
                  hInst,
                  this            //初始化参数
                  );,

在InitProc里面取出该指针:

LRESULT WINAPI CMyWnd::InitProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{  
 if(uMsg == WM_NCCREATE)
 {
  CMyWnd *w = NULL;
  w = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;
  if(w)
  {
   //记录hWnd
   w->m_hWnd = hWnd;
   
   //改变窗口过程为m_thunk
   SetWindowLong(hWnd,GWL_WNDPROC,(LONG)w-CreateThunk());
   return (*(WNDPROC)(w->GetThunk()))(hWnd,uMsg,wParam,lParam);   
  }
 } 
 return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

这样就大功告成。

窗口过程转发流程:

    假设已建立CMyWnd类的窗口对象 CMyWnd *window,初始化完毕后调用window->Create,这时Create的窗口其窗口过程函数是静态CMyWnd::InitWndProc

 

InitWndProc 实现功能:window->Create创建窗口时已把对象this指针放入窗口初始化参数中,在此过程的WM_NCCREATE消息中把this指针取出来:CMyWnd *w = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;记录HWND:w->m_hWnd = hWnd,然后设置窗口过程为w->m_thunk(thunk是一个WNDPROC类型的成员数据,所以可以设置)
└→ window->m_thunk 实现功能:跳转到静态CMyWnd::stdProc,在此之前替换系统的调用参数HWND为this指针
└→ stdProc 实现功能:把HWND转换为窗口类指针:
CMyWnd *w = (CMyWnd*)hWnd;
return w->WindowProc(uMsg,wParam,lParam)
└→ window->WindowProc 实现功能:执行实际的消息处理,窗口句柄已保存在m_hWnd

© 2007 pangwa's Blog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress