C++:模板化继承的递归类:不可能的三重威胁?

use*_*493 6 c++ recursion inheritance templates compilation

所以,假设您有一个递归的基类(例如链表)和派生类.派生类应该重用基类中的构造函数,因为您不想编写冗余代码.你可以尝试一下这个显而易见的事情,它不会起作用:

class Base {
public:

     Base(int size) {
      if (size <= 0) { next = NULL; }
      else { next = new Base(size - 1); }
     }
     void print() { 
      cout << " Base  ";
      if (next != NULL) { next->print(); }
     }
protected:
     Base *next;
};
class Derived: public Base {
public:
     Derived(int size) : Base(size) {} 
     void print() 
      { 
           cout << " Derived ";
           if (next != NULL) 
           { next->print(); }
      }

};
int main()
{

     Derived d2(5);
     d2.print();
     cout << "\n";
     return 0;
}
Run Code Online (Sandbox Code Playgroud)

这不起作用 - 当您实例化Derived时,它构造一个Derived实例,然后调用Base类构造函数,该构造函数在Base实例之后抽出Base实例.如果您运行"main",您将获得:

Derived  Base   Base   Base   Base   Base 
Run Code Online (Sandbox Code Playgroud)

现在,您可以变得聪明并使用以下设计模式:http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern,它将解决您的所有问题.看看以下非常简洁的代码:

template <class targetT, class recursiveT>
class Base {
public:

     Base(targetT size) {
      if (size <= 0) { next = NULL; }
      else { next = new recursiveT(size - 1); }
     }

     void print() { 
      cout << " Base  ";
      if (next != NULL) 
      { next->print(); }
     }

protected:
     recursiveT *next;
};
class Derived: public Base<int, Derived> {
public:
     Derived(int size) : Base<int, Derived>(size) {} 
     void print() 
      { 
           cout << " Derived ";
           if (next != NULL) 
           { next->print(); }
      }
};
int main()
{

     Derived d1(5);
     d1.print();
     cout << "\n";
     return 0;
}
Run Code Online (Sandbox Code Playgroud)

这通过了测试 - 当我们从Derived的构造函数回到Base的构造函数时,templating导致Base抽出Derived的实例.整齐!如果你运行main,你会看到以下内容:

 Derived  Derived  Derived  Derived  Derived  Derived 
Run Code Online (Sandbox Code Playgroud)

就像你想要的一样!

现在事情变得奇怪了.假设我们希望Base和Derived自己模仿; 说它们是链表,我们希望它们保存任意数据.

所以我们可以进一步推动这个:

template <class targetT, class recursiveT>
class Base {
public:

     Base(targetT size) {
      if (size <= 0) { next = NULL; }
      else { next = new recursiveT(size - 1); }
     }

     void print() { 
      cout << " Base  ";
      if (next != NULL) 
      { next->print(); }
     }

protected:
     recursiveT *next;
};
template <class T>
class Derived: public Base<T, Derived<T> > {
public:
     Derived(int size) : Base<T, Derived<T> >(size) {} 
     void print() 
      { 
           cout << " Derived ";
           if (next != NULL) 
           { next->print(); }
      }
}; 
int main()
{

     Derived<int> d1(5);
     d1.print();
     cout << "\n";
     return 0;
}
Run Code Online (Sandbox Code Playgroud)

但令人惊讶的是,编译现在因g ++而失败:

X.cpp: In member function ‘void Derived<T>::print()’:
X.cpp:33: error: ‘next’ was not declared in this scope
Run Code Online (Sandbox Code Playgroud)

有人知道为什么会这样吗?我几乎怀疑g ++是错的,在这里.我有gcc版本4.3.2和gcc版本4.4.1的这个问题.

Jam*_*lis 12

问题是基类Base<T, Derived<T> >是依赖基类,因为它依赖于模板参数,但next不是依赖名,因为它不依赖于模板参数.编译器不会在依赖基类中查找非依赖名称.

您可以next通过以下方式访问它来解决此问题this->:

this->next
Run Code Online (Sandbox Code Playgroud)

您可以从C++ FAQ Lite文章中找到更多信息,"当我的模板派生类使用从模板基类继承的成员时,为什么会出现错误?"

  • @James:两阶段查找是为了尽量减少意外.Sjoerd已经用自由函数`next` vs member-function`next`刷新了主题,但还有更多.查找在声明(非实例化)时发生的非依赖名称,它们不应受到后面声明的任何内容的影响(库编写者无法知道).这是在*Library*标题之前不包含*Your*标题的另一个原因.遗憾的是,VC++在这方面缺乏,并且当从标准兼容编译器迁移时,它可能导致细微的陷阱. (2认同)