成员函数模板放在哪里

发布于 2024-09-30 21:10:46 字数 1895 浏览 1 评论 0原文

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 中。但是对于同样使用 iostream 的成员函数模板 countAndPrint() 该怎么办呢?它在 counter.cpp 中没有用,因为它需要在编译的 counter.o 之外实例化。但是将它放在 counter.h 中意味着任何包含 counter.h 的内容也依次包含 iostream,这似乎是错误的(我承认我可能必须克服这种厌恶)。我还可以将模板代码放入单独的文件中(counter.t?),但这对于代码的其他用户来说会有点令人惊讶。 Lakos 并没有像我想要的那样深入研究这个问题,并且 C++ FAQ 并没有深入探讨实践。所以我想要的是:

  1. 是否有其他方法可以将代码划分为我建议的代码?
  2. 在实践中,什么最有效?

An aspect of C++ that periodically frustrates me is deciding where templates fit between header files (traditionally describing the interface) and implemention (.cpp) files. Templates often need to go in the header, exposing the implementation and sometimes pulling in extra headers which previously only needed to be included in the .cpp file. I encountered this problem yet again recently, and a simplified example of it is shown below.

#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);
}

Note that I intend to use my actual class as a base class, hence the virtual destructor; I doubt it matters, but I've left it in Counter just in case. The resulting output from the above is

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

Now Counter's class declaration could all go in a header file (e.g., counter.h). I can put the implementation of the dtor, which requires iostream, into counter.cpp. But what to do for the member function template countAndPrint(), which also uses iostream? It's no use in counter.cpp since it needs to be instantiated outside of the compiled counter.o. But putting it in counter.h means that anything including counter.h also in turn includes iostream, which just seems wrong (and I accept that I may just have to get over this aversion). I could also put the template code into a separate file (counter.t?), but that would be a bit surprising to other users of the code. Lakos doesn't really go into this as much as I'd like, and the C++ FAQ doesn't go into best practice. So what I'm after is:

  1. are there any alternatives for dividing the code to those I've suggested?
  2. in practice, what works best?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

乖不如嘢 2024-10-07 21:10:46

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

  • 私有成员模板应在 .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;
      }
      // ...
    }
  }
};

A rule of thumb (the reason of which should be clear).

  • Private member templates should be defined in the .cpp file (unless they need to be callable by friends of your class template).
  • Non-private member templates should be defined in headers, unless they are explicitly instantiated.

You can often avoid having to include lots of headers by making names be dependent, thus delaying lookup and/or determination of their meaning. This way, you need the complete set of headers only at the point of instantiation. As an example

#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_;
  }
};

This is what I used for implementing a visitor pattern that uses CRTP. It looked like this initially

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;
      }
      // ...
    }
  }
};

This will need the headers of all statement classes because of those static casts. So I have made the types be dependent, and then I only need forward declarations

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;
      }
      // ...
    }
  }
};
誰認得朕 2024-10-07 21:10:46

Google 样式指南建议将模板代码放在“counter-inl”中.h”文件。如果您想对包含内容非常小心,这可能是最好的方法。

然而,客户端通过“意外”获得包含的 iostream 标头可能只是将所有类的代码放在一个逻辑位置中所付出的很小的代价——至少如果您只有一个成员函数模板的话。

The Google Style Guide suggests putting the template code in a "counter-inl.h" file. If you want to be very careful about your includes, that might be the best way.

However, clients getting an included iostream header by "accident" is probably a small price to pay for having all your class's code in a single logical place—at least if you only have a single member function template.

ゃ人海孤独症 2024-10-07 21:10:46

实际上,您唯一的选择是将所有模板代码放置在标头中,或者将模板代码放置在 .tcc 文件中,并在标头末尾包含该文件。

另外,如果可能的话,您应该尽量避免在标头中使用 #includeing ,因为这会严重影响编译时间。毕竟,标头通常由多个实现文件#include组成。标头中唯一需要的代码是模板和内联代码。析构函数不需要位于标头中。

Practically your only options are to place all template code in a header, or to place template code in a .tcc file and include that file at the end of your header.

Also, if possible you should try to avoid #includeing <iostream> in headers, because this has a significant toll on compile-time. Headers are often #included by multiple implementation files, after all. The only code you need in your header is template and inline code. The destructor doesn't need to be in the header.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文