代码之家  ›  专栏  ›  技术社区  ›  beldaz Nicolas W.

成员函数模板的放置位置

  •  4
  • beldaz Nicolas W.  · 技术社区  · 14 年前

    周期性地挫败我的C++的一个方面是决定模板之间的模板(传统上描述接口)和实现(.CPP)文件之间的位置。模板通常需要放在头中,公开实现,有时还需要拉入以前只需要包含在.cpp文件中的额外头。我最近又遇到了这个问题,下面是一个简单的例子。

    #include <iostream> // for ~Counter() and countAndPrint()
    
    class Counter
    {
      unsigned int count_;
    public:
      Counter() : count_(0) {}
      virtual ~Counter();
    
      template<class T>
      void
      countAndPrint(const T&a);
    };
    
    Counter::~Counter() {
        std::cout << "total count=" << count_ << "\n";
    }
    
    template<class T>
    void
    Counter::countAndPrint(const T&a) {
      ++count_;
      std::cout << "counted: "<< a << "\n";
    }
    
    // Simple example class to use with Counter::countAndPrint
    class IntPair {
      int a_;
      int b_;
    public:
      IntPair(int a, int b) : a_(a), b_(b) {}
      friend std::ostream &
      operator<<(std::ostream &o, const IntPair &ip) {
        return o << "(" << ip.a_ << "," << ip.b_ << ")";
      }
    };
    
    int main() {
      Counter ex;
      int i = 5;
      ex.countAndPrint(i);
      double d=3.2;
      ex.countAndPrint(d);
      IntPair ip(2,4);
      ex.countAndPrint(ip);
    }
    

    请注意,我打算使用我的实际类作为基类,因此使用虚拟析构函数;我怀疑它是否重要,但为了以防万一,我将它留在了计数器中。上面的结果是

    counted: 5
    counted: 3.2
    counted: (2,4)
    total count=3
    

    Counter 的类声明都可以放在头文件中(例如counter.h)。我可以将需要iostream的dtor的实现放入counter.cpp。但是如何处理成员函数模板 countAndPrint() Lakos 没有我想的那么多,而且 C++ FAQ 没有进入最佳实践。所以我想要的是:

    1. 有没有其他方法可以将代码与我建议的代码分开?
    2. 实际上,什么最有效?
    3 回复  |  直到 14 年前
        1
  •  5
  •   Johannes Schaub - litb    14 年前

    经验法则(其原因应该很清楚)。

    • 私有成员模板应该在.cpp文件中定义(除非类模板的朋友可以调用它们)。
    • 非私有成员模板应该在头中定义,除非它们是显式实例化的。

    #include <iosfwd> // suffices
    
    class Counter
    {
      unsigned int count_;
    public:
      Counter() : count_(0) {}
      virtual ~Counter();
    
      // in the .cpp file, this returns std::cout
      std::ostream &getcout();
    
      // makes a type artificially dependent
      template<typename T, typename> struct ignore { typedef T type; };
    
      template<class T>
      void countAndPrint(const T&a) {
        typename ignore<std::ostream, T>::type &cout = getcout();
        cout << count_;
      }
    };
    

    这就是我用来实现使用CRTP的访问者模式的方法。最初看起来是这样的

    template<typename Derived>
    struct Visitor {
      Derived *getd() { return static_cast<Derived*>(this); }
      void visit(Stmt *s) {
        switch(s->getKind()) {
          case IfStmtKind: {
            getd()->visitStmt(static_cast<IfStmt*>(s));
            break;
          }
          case WhileStmtKind: {
            getd()->visitStmt(static_cast<WhileStmt*>(s));
            break;
          }
          // ...
        }
      }
    };
    

    template<typename T, typename> struct ignore { typedef T type; };
    
    template<typename Derived>
    struct Visitor {
      Derived *getd() { return static_cast<Derived*>(this); }
      void visit(Stmt *s) {
        typename ignore<Stmt, Derived>::type *sd = s;
        switch(s->getKind()) {
          case IfStmtKind: {
            getd()->visitStmt(static_cast<IfStmt*>(sd));
            break;
          }
          case WhileStmtKind: {
            getd()->visitStmt(static_cast<WhileStmt*>(sd));
            break;
          }
          // ...
        }
      }
    };
    
        2
  •  1
  •   John Calsbeek    14 年前

    这个 Google Style Guide 建议将模板代码放在“counter inl.h”文件中。如果你想非常小心你的包,这可能是最好的方法。

    但是,客户得到了 iostream 头部“意外”可能是一个小代价,至少在只有一个成员函数模板的情况下,将类的所有代码放在一个逻辑位置。

        3
  •  1
  •   wilhelmtell    14 年前

    实际上,您唯一的选择是将所有模板代码放在标题中,或者将模板代码放在 .tcc 在你头的最后 .

    #include 惯性导航与制导 <iostream> 在头中,因为这对编译时间有很大的影响。标题通常是 毕竟是多个实现文件。头文件中唯一需要的代码是模板和内联代码。析构函数不需要在头中。