Связывание шаблонов и разделение файлов .h и .cc - PullRequest
1 голос
/ 01 декабря 2011

У меня было некоторое чтение в разработке кода шаблона, есть вопрос по этому поводу. Большинство решений проблем, связанных с проектированием кода в виде шаблонов, кажутся либо:

  1. Поместить определения прототипов в заголовочный файл
  2. Используйте ключевое слово экспорта , как это (для которого требуется дополнительная опция компилятора)
  3. Конкретно изложите, как шаблоны будут использоваться в файле .cc / .cpp.

Например:

// File: foo_impl.cc
// We're working with Class Foo
#include "foo.cc"

template class Foo <int>;
template class Foo <string>;
// etc.

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

В main.cc:

#include <iostream>
#include "foo.h"

using namespace std;

int main (void) {
   Foo <string> f ("hello world");
   string s = f.get ();
   cout << s << endl;

   return 0;
}

В foo.h:

#ifndef FOO_H
#define FOO_H

template <class T>
class Foo {
   public:
      Foo (T);
      T get ();

   private:
      T data;
};

#endif

#include "foo.cc"

In foo.cc:

#ifndef FOO_CC
#define FOO_CC

#include "foo.h"

template <class T>
Foo :: Foo (T stuff) {
   data = stuff;
}

template <class T>
T Foo <T> :: get () {
   return data;
}

#endif

Мне удалось скомпилировать код со всеми предупреждениями в gcc 4.1.2. Спасибо!

Ответы [ 6 ]

2 голосов
/ 01 декабря 2011

Давайте начнем с базовой идеологии файлов .h и .cc. При создании библиотек идея состоит в том, чтобы делиться только вашими заголовочными файлами, а не вашей реализацией (имеется в виду файлы .cc). Это также основы инкапсуляции ООП, абстракции и т. Д., Чтобы скрыть детали реализации.

Теперь шаблоны в C ++ нарушают этот принцип, bcoz C ++ - это скомпилированный язык. И компилятор генерирует весь необходимый код во время компиляции. Теперь, чтобы придерживаться ООП, мы получаем хрупкие шаблоны, которые на 100% не являются общими по своей природе.

Разделяйте декларацию и определения (реализация SHARING)

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

foo.h

Это простой файл с включением foo_impl.h.

#ifndef FOO_H
#define FOO_H
template <class T>
class Foo {
  public:
    Foo (T);
    T get();
  private:
    T data;
};

#include "foo_impl.h"
#endif

foo_impl.h

Этот немного отличается от норм. Здесь мы не охраняем содержимое заголовочного файла. Вместо этого мы выдадим ошибку, если кто-то включит foo_impl.h напрямую (что в нашем случае не имеет смысла).

#ifndef FOO_H
#error 'foo_impl.h' is not supposed to be included directly. Include 'foo.h' instead.
#endif

template <class T>
Foo <T> :: Foo (T stuff) {
   data = stuff;
}

template <class T>
T Foo <T> :: get () {
   return data;
}

Теперь, если кто-то попытается включить foo_impl.h напрямую, получит ошибку вроде:

foo_impl.h:2:2: error: #error 'foo_impl.h' is not supposed to be included directly. Include 'foo.h' instead.

ПЛЮСЫ:

  • Разделение задач, реализация и декларации находятся в отдельных файлах.
  • Безопасный файл реализации защиты от случайного включения.
  • Файл заголовка, используемый для включения, не раздувается с кодом реализации.

МИНУСЫ:

  • Как уже упоминалось выше, необходимо поделиться реализацией.

ПРИМЕЧАНИЕ. Если вы не делитесь кодом для шаблонов, я думаю, вы уже знаете, что вам нужно объявить все возможные типы, в которых конечный пользователь может его использовать.

2 голосов
/ 01 декабря 2011

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

Допустим, в вашем примере у вас также был файл заголовка (bar.h), который просто объявляет класс, содержащий элемент данных Foo.

Каждый раз, когда вы изменяете определение класса Foo, вы вызываете перекомпиляцию любого, кто включает bar.h, даже если они не заботятся об определении Foo. Тем не менее, bar.cpp - это, вероятно, место, где вы на самом деле реализуете материал, и этот файл ДОЛЖЕН включать реализацию вашего шаблона. Это кажется тривиальным в небольших проектах, но становится источником головной боли в больших проектах, которые постоянно перекомпилируют файлы без причины. Я видел, как люди бросали SSD и Incredibuild на вещи, которые можно было бы исправить с помощью простых форвардов и лучшего управления заголовками.

Лично я использую .imp.h для реализации моих шаблонов. Включение файлов cc или cpp мне кажется противным.

Например (извините за ошибки компиляции.;))

// foo.h
#ifndef foo_h
#define foo_h
template< typename T >
struct Foo
{
   Foo( T value );
   void print();
   T _value;     
};
#endif

//foo.imp.h
#ifndef foo_imp_h
#define foo_imp_h
#include "foo.h"
#include <iostream>
template< typename T >
Foo< T >::Foo( T value ) : _value( value ) {}
void Foo< T >::print() { std::cout << _value << std::endl; }
#endif

// bar.h
#ifndef bar_h
#define bar_h
#include "foo.h"
struct Bar {
   Foo< int > _intFoo;
   Foo< double > _doubleFoo;
   void print();
};
#endif

// bar.cpp
#include "bar.h"
#include "foo.imp.h"
void Bar::print()
{
   _intFoo.print();
   _doubleFoo.print();
}

// foobar.cpp
#include "bar.h"
void foobar()
{
   Bar bar;
   bar.print();
}

Если бы определение foo было включено в foo.h или в него, bar.cpp и foobar.cpp были бы перекомпилированы. Так как только bar.cpp связан с реализацией Foo, разделение определения и объявления Foo на два файла и отсутствие foo.h, включающего в конце foo.imp.h, спасли меня от перекомпиляции foobar.cpp.

Это то, что происходит постоянно в проектах, и его очень легко избежать, следуя правилу .h / .imp.h, которое я объяснил выше. Причина, по которой вы никогда не видите этого в таких вещах, как STL или boost, заключается в том, что вы не изменяете эти файлы. Не имеет значения, находятся ли они в одном или двух файлах. Но в ваших собственных проектах вы будете постоянно изменять определения своих шаблонов, и именно так вы сокращаете время перекомпиляции.

Если вы уже знаете заранее, какие типы фактически будут использоваться с вашим шаблоном, то даже не беспокойтесь о файле .imp.h. Поместите все в .cpp и сделайте это в конце

// foo.cpp
// Implementation goes here.
// You might need to put something in front so that it gets exported from your DLL,
// depening on the platform
template class foo< int >;
template class foo< double >;
1 голос
/ 01 декабря 2011

Как правило, на разделение интерфейса и реализацию шаблонов влияют #include реализация шаблонов в конце заголовка, но есть три проблемы с тем, как вы это делаете:

  1. Вы включаете файл .h в файл .cc.Не;в файле реализации не должно быть ничего, кроме определений функций.
  2. Ваш файл .cc не должен называться .cc, он должен называться .template или что-то подобное, чтобы люди знали, что его не нужно компилировать(подобно тому, как заголовки не должны компилироваться)
  3. #include "foo.cc" в foo.h должно быть внутри включающих охранников, а не снаружи., нет никакой дополнительной работы для пользователя.Все, что вы делаете, это #include заголовок, и все готово.Вы не компилируете реализацию.
1 голос
/ 01 декабря 2011

Включение файлов .cc является плохой новостью и не позволяет отделить реализацию от декларации.

Определение шаблонов в заголовках:

#ifndef FOO_H
#define FOO_H

template <class T>
class Foo {
   public:
      Foo (T);
      T get ();

   private:
      T data;
};

// implementation:


template <class T>
Foo :: Foo (T stuff) {
   data = stuff;
}

template <class T>
T Foo <T> :: get () {
   return data;
}

#endif

Если вы действительно предпочитаете 2 файла, сделайте второй тоже .h. Назовите его foo_impl.h или как-нибудь.

0 голосов
/ 01 декабря 2011

ключевое слово экспорта устарело в c ++ 11.Таким образом, вы получите устаревший код.Вы помещаете свои определения в сам файл заголовка.

0 голосов
/ 01 декабря 2011

Поскольку вы включаете foo.cc в foo.h, вы упростите свою жизнь, поместив all код в foo.h и избавившись от foo.cc.Нет никакого преимущества от разделения кода на две части.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...