Где определить функцию шаблона члена класса C ++ и функторы, которые ее создают?

У меня есть класс Foo, который используется в небольшом автономном проекте. Он имеет определение класса в Foo.h с реализацией функций-членов класса в файле реализации Foo.cpp.

Первый вопрос - одна из функций-членов класса Foo - это шаблонный метод Foo :: doSomething (), правильно ли, что реализация этого метода должна появляться с объявлением функции в Foo.h?

Параметр шаблона, с которым будет создан экземпляр Foo :: doSomething (), является одним из двух типов Functor - class CalcA и CalcB.

Нужно ли мне:

  • (A) поместите определение и реализацию двух классов Functor вместе в Foo.cpp (где они фактически используются реализацией других функций-членов Foo для вызова Foo :: doSomething).
  • (B) поместите определение и реализацию двух классов Functor в Foo.h.
  • (C) следует ли разделить определение и реализацию двух функторов в Foo.h и Foo.cpp, как это было бы с обычным классом?

person Paul Caheny    schedule 30.11.2010    source источник


Ответы (4)


Общее правило:

Если foo :: doSomething () используется вне foo.cpp (т. Е. Если он общедоступный или защищенный, как правило), он должен идти в заголовке.

Если нет, то вставка файла cpp - это нормально и даже хорошая идея (поскольку он убирает беспорядок от файла заголовка).

Итак, если функторы используются только в файле cpp, обязательно поместите туда и функцию шаблона. Если что-то изменится, всегда можно будет провести рефакторинг.

person Macke    schedule 30.11.2010
comment
В случае, когда шаблон необходим только в одном файле CPP, я бы поместил декларацию и определение только в этот файл. Если это шаблон функции-члена, а остальная часть класса используется в других единицах перевода, я бы определенно поместил определение в файл .h. Я бы даже был обеспокоен неопределенным или определяемым реализацией поведением, если бы этого не было сделано. - person John Dibling; 30.11.2010
comment
@Marcus, я должен был упомянуть, что в исходном вопросе - да Foo :: doSomething () является частной функцией-членом шаблона класса Foo. - person Paul Caheny; 30.11.2010
comment
@ Джон, я не понимаю твоей точки зрения, не могли бы вы немного уточнить? В моем случае у меня есть частная функция-член шаблона класса Foo, поэтому она используется только реализацией класса Foo в Foo.cpp. Так что, похоже, это соответствует первому падежу / предложению в вашем комментарии. Но моя ситуация также соответствует описанию во втором предложении вашего комментария (шаблон функции-члена и остальная часть класса используются в других единицах перевода), где вы говорите, чтобы поместить его в файл .h. - person Paul Caheny; 30.11.2010
comment
@Czarak: Я считаю, что в вашем случае вы должны по умолчанию поместить определение шаблона функции-члена в файл .h. @ Маркус может со мной не согласиться. Вы должны будете сделать суждение. - person John Dibling; 30.11.2010
comment
Хотя это private, мне кажется немного рискованным и неприятным иметь не полностью определенные объекты, плавающие вокруг разных единиц перевода. - person John Dibling; 30.11.2010
comment
Я довольно прагматично отношусь к неопределенному поведению в этом случае, и у меня есть 10-летний опыт, подтверждающий это. Невидимая реализация функции-члена никоим образом не влияет на объект, и, учитывая разумные методы кодирования (соответствие файлам h / cpp), не могут быть дважды определенные функции. OTOH, если вы пишете код АЭС, непременно поместите его в шапку. :) - person Macke; 30.11.2010
comment
@John Зачем помещать частные определения в файл заголовка? Я не понимаю, что вы имеете в виду, когда мне кажется немного рискованным и вонючим иметь не полностью определенные объекты, плавающие вокруг. Люди, не входящие в вашу TU .cpp, не могут вызывать частный шаблон участника, поэтому у вас не будет никаких проблем. Вы просто перетащите больше зависимостей и, возможно, ненужные файлы #include в заголовок, но это никуда не годится. - person Johannes Schaub - litb; 01.12.2010

Сначала вы должны понять механизм шаблонов. Шаблоны не компилируются, они создаются при использовании, а затем их экземпляр компилируется. Таким образом, компилятор должен иметь полное определение шаблона в каждом модуле, использующем функцию шаблона, чтобы сначала создать их экземпляры в соответствии с переданными вами параметрами.

Для решения вашей проблемы есть три решения, но вы увидите, что оба они приводят к одному и тому же результату. Либо вы реализуете все свои шаблоны в файле заголовка внутри определения класса (мы используем суффикс .hxx вместо .h, чтобы уточнить, что они содержат определения шаблонов):

// Foo.hxx

#ifndef __FOO_HXX__
#define __FOO_HXX__

class Foo {
  public:
   template <class T>    
   void bar(const T& t) {
      t.doSomething();
   }
 };
#endif

Или вы можете передать определение из класса, но все еще в файле заголовка:

// Foo.hxx

#ifndef __FOO_HXX__
#define __FOO_HXX__

class Foo {
    public:
       template <class T>    
       void bar(const T&);
};

template <class T>
void Foo::bar(const T& t) {
   t.doSomething();
}
#endif

Наконец, вы можете реализовать тела шаблонных методов во внешнем файле (с префиксом .cxx по той же причине). Он будет содержать тела методов, но не будет включать "Foo.hxx". Вместо этого это «Foo.hxx», который будет включать «Foo.cxx» после определения класса. Таким образом, когда компилятор разрешает директиву #include, он находит все определение шаблона в том же модуле, что позволяет ему создать его экземпляр:

// Foo.hxx

#ifndef __FOO_HXX__
#define __FOO_HXX__
class Foo {
    public:
       template <class T>    
       void bar(const T&);
};

#include "Foo.cxx"

#endif

// Foo.cxx
template <class T>
void Foo::bar(const T& t) {
   t.doSomething();
}

Выбор между этими тремя способами реализации шаблонов скорее вопрос читабельности (и вкуса).
Второй и третий эквивалентны с точки зрения сгенерированного кода, но я бы предпочел не использовать решение с файлом cxx, потому что оно часто приводит к тупым ошибкам, когда вы забываете инвертировать include.

Более того, известные библиотеки C ++, такие как STL или Boost, предлагают свой код только в файлах заголовков, что является признаком хорошего дизайна. Используя внешнее определение внутри заголовков, вы уточняете определение своего класса. Вы также запрещаете компилятору автоматически встраивать методы, что, по словам Херба Саттера, http://www.gotw.ca/gotw/033.htm

person jopasserat    schedule 30.11.2010
comment
Спасибо за ответ. В моем исходном вопросе я должен был упомянуть, что функция-член шаблона в моей ситуации является частной функцией-членом класса Foo. Отдельно от этого - учитывая три предложения в вашем ответе, каковы преимущества / недостатки каждого из них? Основное различие, которое я вижу, заключается в том, что в вашем первом предложении функция-член будет неявно встроенной, а в следующих двух - нет, это правильно? Какие преимущества / недостатки у второго и третьего есть друг перед другом? - person Paul Caheny; 30.11.2010
comment
@Czarak: вы правы - ›методы, определенные в теле класса, автоматически встраиваются (см. Заголовок stackoverflow.com/questions/1443982/ для получения дополнительных сведений о встраивании). Я отредактировал свой ответ, чтобы добавить уточнения, которые вы просили. Надеюсь, это вам помогло. - person jopasserat; 01.12.2010

По умолчанию я помещаю определение шаблонов функций-членов прямо в файл .h, например:

class Foo
{
public: 
  template<typename T> void DoSomething(T t);
};

// ... later...

template<typename T>
void Foo::DoSomething(T t)
{
  // ...
}

Если это неоптимально для конкретного случая, я бы предпринял более героические меры. Начиная с #include-ing файл .inc с определением в конце файла .h или, возможно, даже выполняя явные экземпляры в файлах .cpp, где мне нужно было использовать шаблоны функций-членов.

person John Dibling    schedule 30.11.2010
comment
ИМХО, это немного переоформление, если это всего лишь одна функция. Но в любом случае это тема для велосипедистов. :) - person Macke; 30.11.2010
comment
@Marcus: Что конкретно такое чрезмерное проектирование? - person John Dibling; 30.11.2010
comment
@Marcus: Думаю, я понимаю, о чем ты сейчас говоришь - person John Dibling; 30.11.2010

Определение метода шаблона действительно должно быть в файле заголовка того класса, к которому он принадлежит.

Нравится:

class MyClass
{
    template <typename T>
    void foo(const T&)
    {
        // Definition
    }
};

Или вот так (обратите внимание, что определение метода шаблона может быть включено из отдельного файла после объявления класса)

class MyClass
{
    template <typename T> void foo(const T&);
};

template <typename T>
void MyClass::foo(const T&)
{
    // Definition
}

Остальное зависит от стиля, о котором вы договорились, и ваших потребностей.

Я бы поместил объявление функтора (или даже определение, если они простые) в заголовок, если я использую их не только в Foo или если Foo имеет их как член класса.

person Palmik    schedule 30.11.2010