给 greenTown 个交代,自己也学习一下

  答应你有时间研究一下那篇文章上的程序的,不过过节加上工作、杂事就耽误了这么久,真是过意不去:)

  其实我自己用 WTL 的时候也没有深究过其实现,这也是个学习的好机会:)文章里的程序如下:

  1. template<class T>
  2.  class B1
  3.  {
  4.  public:
  5.    void SayHi()
  6.    {
  7.      T *pT = static_cast<t *>( this )// 技巧
  8.      pT->PrintClassName();
  9.    }
  10.  protected:
  11.    void PrintClassName() { cout << "This is B1\n"; }
  12.  };
  13.  class D1 : public B1<d1>
  14.  {};
  15.  class D2 : public B1<d2>
  16.  {
  17.  protected:
  18.    void PrintClassName() { cout << "This is D2\n"; }
  19.  };
  20.  int main( void )
  21.  {
  22.    D1 d1;
  23.    D2 d2;
  24.    d1.SayHi()// This is B1
  25.    d2.SayHi()// This is D2
  26.    return( 0 );
  27.  }

  VC6 下编译会报“error C2248: ‘PrintClassName’ : cannot access protected member declared in class ‘D2’”的错误,你的疑问就在这里。我用有限的 template 知识分析了一下觉得这是个笔误:)我觉得 D2 中对 PrintClassName() 的声明一定要是 public 的。

  其实上面的例子更接近下面这个(我叫它例 1):

  1. class D2
  2.  {
  3.  protected:
  4.    void PrintClassName() { cout << "This is D2\n"; }
  5.  };
  6.  class B1
  7.  {
  8.  public:
  9.    void SayHi( D2 *pD2 ) { pD2->PrintClassName(); }  // error
  10.  };
  11.  int main( void )
  12.  {
  13.    D2 d2;
  14.    B1 b1;
  15.    b1.SayHi( &d2 );
  16.    return( 0 );
  17.  }

  而不是这个(我叫它例 2):

  1. class D2
  2.  {
  3.  protected:
  4.    void PrintClassName() { cout << "This is D2\n"; }
  5.  };
  6.  class B1 : public D2
  7.  {
  8.  public:
  9.    void SayHi() { PrintClassName(); }  // OK
  10.  };
  11.  int main( void )
  12.  {
  13.    B1 b1;
  14.    b1.SayHi();
  15.    return( 0 );
  16.  }

  模板类 B1 只是使用 D2 进行了实例化,并非继承自 D2(正好相反是 D2 继承自 B1)。所以对 B1 来说,D2 对于它更像是例 1 里面的情况(其实把例 1 里面的 B1 写成模板类更贴切一点,但是现在这样更好理解:))。

  如果是 B1 和 D2 的关系像例 2 里面那样,是 B1 继承自 D2(公有继承),派生类访问基类保护成员函数当然就是没问题的!但是文章里的程序更像是例 1 里的情况,B1 仅仅知道 D2 而并非从 D2 派生而来,要直接访问 D2 中的受保护成员函数当然是不可以的!

  当然 D2::PrintClassName() 确实是覆盖了基类的同名成员函数,但从实践来看编译器是优先对模板实例化进行了解析而非继承关系(在我的印象中,C++98 中对模板的规范好像还不够细致)。为了验证我的想法而排除编译器的因素,我又用 MinGW(相当于 Win32 平台下的 gcc)编译了文章中的例子,结果仍然是错误的,而出错原因跟在 VC 里面是一样的。当然,这个测试还不全面,有时间我再多用几款编译器试试看。

  呵呵,上面就是我的理解和解释,不知是否有够说服力?我的模板知识确实有限,而且只测了两款编译器,欢迎有不同意见和想法的高人指点:)

  弄这个东西的过程中我想起了刚学 C++ 时一个搞晕我的地方:

  1. class A
  2.  {
  3.  public:
  4.    void SayHi( A *pA ) { pA->PrintClassName(); }
  5.  private:
  6.    void PrintClassName() { cout << "This is A\n"; }
  7.  };
  8.  int main( void )
  9.  {
  10.    A a1;
  11.    A a2;
  12.    a2.SayHi( &a1 );
  13.    return( 0 );
  14.  }

我刚学 C++ 的时候看到这个正确的例程就是想不通为什么 A::SayHi() 可以直接用 -> 操作符访问到其它对象的私有成员函数:)后来看到 void SayHi( A *pA ) { pA->A::PrintClassName(); } 这种写法马上就想通了:)

  WTL 里面对模板类还有一种形式被大量运用(尤其是对控件的封装):

  1. template <class TBase>
  2.  class CButtonT : public TBase
  3.  {
  4.    ... ...
  5.  }

typedef CButtonT CButton;
这里面就没有什么技巧可言了,要用按钮控件的时候就直接用 CButton,我觉得除了基类可代换之外跟 class CButton : public CWindow 没有任何区别,但是也不会有人想要去重写一个有 N 多 wrap 成员函数的 CWindow 吧?!

Leave a Reply