Способи організації інтерфейсу та реалізації в C ++


12

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

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

Хоча другу версію простіше писати, вона змішує інтерфейс з реалізацією.

Вихідні файли, що містять файли заголовків, повинні бути перекомпільовані щоразу, коли файли заголовків змінюються. У першій версії ви можете змінити файл заголовка, лише якщо вам потрібно змінити інтерфейс. У другій версії ви зміните файл заголовка, якщо вам потрібно змінити інтерфейс або реалізацію.

Крім того, що ви не повинні розкривати деталі реалізації , ви отримаєте непотрібну перекомпіляцію з другою версією.


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

Це може бути несуттєвим або істинним, якщо ваш виконуваний файл стає більшим, але більше коду у файлах заголовків дозволяє компілятору більше шансів оптимізувати для швидкості.

Якщо ви вирішили писати бібліотеку лише в заголовку , ця тема є однією з ваших проблем.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.