Где разместить шаблон функции-члена - PullRequest
4 голосов
/ 06 ноября 2010

Аспект 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);
}

Обратите внимание, что я намерен использовать свой фактический класс в качестве базового класса, следовательно, виртуальный деструктор;Я сомневаюсь, что это имеет значение, но я оставил это в Counter на всякий случай.Результат, полученный в результате вышеописанного:

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

Теперь объявление класса Counter может быть помещено в заголовочный файл (например, counter.h).Я могу поместить реализацию dtor, которая требует iostream, в counter.cpp.Но что делать с шаблоном функции-члена countAndPrint(), который также использует iostream?Он не используется в counter.cpp, поскольку его необходимо создавать вне скомпилированного counter.o.Но добавление его в counter.h означает, что все, в том числе и counter.h, также включает iostream, что кажется неправильным (и я признаю, что мне, возможно, придется преодолеть это отвращение).Я мог бы также поместить код шаблона в отдельный файл (counter.t?), Но это было бы немного удивительно для других пользователей кода. Lakos на самом деле не так важен, как хотелось бы, а C ++ FAQ не подходит для лучшей практики.Итак, что мне нужно:

  1. Есть ли какие-нибудь альтернативы для разделения кода на те, что я предложил?
  2. на практике, что работает лучше всего?

Ответы [ 3 ]

5 голосов
/ 06 ноября 2010

Эмпирическое правило (причина которого должна быть понятна).

  • Шаблоны закрытых элементов должны быть определены в файле .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;
      }
      // ...
    }
  }
};
1 голос
/ 06 ноября 2010

Практически ваши единственные варианты - поместить весь код шаблона в заголовок или поместить код шаблона в файл .tcc и включить этот файл в конец заголовка .

* 1005.* Также, если возможно, вы должны стараться избегать #include ing <iostream> в заголовках, потому что это существенно влияет на время компиляции.В конце концов, заголовки часто бывают #include d несколькими файлами реализации.Единственный код, который вам нужен в вашем заголовке - это шаблон и встроенный код.Деструктор не обязательно должен быть в заголовке.
1 голос
/ 06 ноября 2010

Руководство по стилю Google предлагает поместить код шаблона в файл "counter-inl.h".Если вы хотите быть очень осторожными в отношении ваших включений, это может быть лучшим способом.

Однако клиенты, получающие включенный заголовок iostream "случайно", вероятно, являются небольшой ценой, чтобы заплатить за то, что у вас есть все классы.код в одном логическом месте - по крайней мере, если у вас есть только один шаблон функции-члена.

...