Способы организации интерфейса и реализации на C ++

12

Я видел, что в C ++ есть несколько различных парадигм, касающихся того, что входит в заголовочный файл и что в файл cpp. AFAIK, большинство людей, особенно те из C, делают:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

Тем не менее, мои преподаватели обычно преподают C ++ начинающим, как это:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Первоначально пришедший из Java, я также всегда придерживался этого второго способа по нескольким причинам, таким как то, что мне нужно что-то менять только в одном месте, если меняются имена интерфейса или метода, что мне нравятся разные отступы в классах, когда я посмотрите на их реализацию, и что я считаю имена более читабельными по fooсравнению с foo::foo.

Я хочу собрать "за" и "против" в любом случае. Может быть, есть еще другие способы?

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

Феликс Домбек
источник
2
foo.cppтеперь не имеет ничего общего с вашим fooклассом и должен быть оставлен пустым (возможно, но, #includeчтобы сделать вашу систему сборки счастливой).
Бенджамин Баннье
2
Ваши преподаватели безумны.
Гонки легкости на орбите

Ответы:

16

В то время как вторую версию легче написать, она смешивает интерфейс с реализацией.

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

Кроме того, вы не должны раскрывать детали реализации , вы получите ненужную перекомпиляцию со второй версией.

LennyProgrammers
источник
1
+1 Мой профилировщик не обрабатывает код, размещенный в заголовочных файлах - это тоже важная причина.
Евгений,
Если вы увидите мой ответ на этот вопрос programmers.stackexchange.com/questions/4573/… вы увидите, как это во многом зависит от семантики класса, то есть от того, что будет его использовать (в частности, если это открытая часть ваш пользовательский интерфейс и сколько других классов в вашей системе используют его напрямую).
CashCow
3

Я сделал это второй путь назад в 93 -95. Потребовалось несколько минут, чтобы перекомпилировать небольшое приложение с 5-10 функциями / файлами (на том же 486 ПК ... и нет, я тоже не знал о классах, мне было всего 14-15 лет, и интернета не было ) ,

Итак, то, чему вы учите новичков и что вы используете профессионально, - это совершенно разные методы, особенно в C ++.

Я думаю, что сравнение между C ++ и автомобилем F1 уместно. Вы не ставите новичков в машину F1 (которая даже не заводится, если вы предварительно не прогреете двигатель до 80-95 градусов по Цельсию).

Не учите C ++ как первый язык. Вы должны быть достаточно опытны, чтобы знать, почему вариант 2 хуже, чем вариант 1 в целом, знать немного, что означает статическая компиляция / компоновка, и, таким образом, понимать, почему C ++ предпочитает его первым способом.

Маке
источник
Этот ответ был бы еще лучше, если бы вы немного подробнее остановились на статической компиляции / компоновке (тогда я еще этого не знал!)
Феликс Домбек
2

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

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

Это преимущество, вероятно, перевесит тот факт, что если вы изменяете реализацию функции, вам необходимо перестроить весь исходный код, который ее использует. В облегченной природе класса вы не будете изменять реализацию. Если вы добавите новый метод, вам все равно придется изменить заголовок.

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

У него все еще есть свои преимущества, в частности:

  • Если это код «общей функциональности», вы можете просто включить заголовок и использовать его из нескольких проектов, не связываясь с библиотекой, содержащей его источник.

Недостатком встраивания становится проблема, когда это означает, что вы должны внести в реализацию особенности реализации в свой заголовок, то есть вы должны начать включать дополнительные заголовки.

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

Дойная корова
источник
1

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

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

davidvandebunte
источник