Розділення коду класу на заголовок та файл cpp


169

Мене бентежить, як розділити код реалізації та декларації простого класу на новий заголовок та файл cpp. Наприклад, як би я розділив код для наступного класу?

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y)
  {
    gx = x;
    gy = y;
  }

  int getSum()
  {
    return gx + gy;
  }
};

12
Лише пара коментарів: Конструктор завжди повинен використовувати список ініціалізації замість встановлення членів у тілі. Для хорошого та простого пояснення дивіться: codeguru.com/forum/showthread.php?t=464084 Також публічно поле у ​​верхній частині, як мінімум, у більшості місць. Це нічого не вплине, але оскільки публічні поля - це документація вашого класу, має сенс мати це вгорі.
martiert

2
@martiert Наявність public:членів у верхній частині може вплинути на багато , якщо користувач перемістить їх відповідно до цієї поради - але мав упорядкований залежність між членами і ще не знав, що члени ініціалізуються у порядку їх декларації ;-)
underscore_d

1
@underscore_d це правда. Але знову ж таки, ми всі компілюємо попередження як помилки та всі попередження, про які ми можемо придумати, правда? Принаймні, це скаже тобі, що ти це накручуєш, але так, люди використовують спосіб невеликих попереджень і просто ігнорують їх :(
martiert

@martiert Добре, начебто забув, що генерує попередження - якщо б тільки попередження читали більшість :-) Я використовую їх і намагаюся кодувати їх все подалі. Деякі з них неминучі - тому я кажу "дякую за попередження, але я знаю, що роблю!" - але більшість найкраще виправити, щоб пізніше не плутати.
підкреслюйте_d

Наявність публічних полів на вершині - це просто стиль, який, на жаль, прийняв занадто багато. Крім того, потрібно пам’ятати про деякі речі, як згадував @martiert.
Василіс

Відповіді:


232

Декларація класу переходить у файл заголовка. Важливо, щоб ви додали #ifndefвключені охоронці, або якщо ви перебуваєте на платформі MS, ви також можете використовувати #pragma once. Також я пропустив приватне, за замовчуванням члени класу C ++ є приватними.

// A2DD.h
#ifndef A2DD_H
#define A2DD_H

class A2DD
{
  int gx;
  int gy;

public:
  A2DD(int x,int y);
  int getSum();

};

#endif

і реалізація йде у файлі CPP:

// A2DD.cpp
#include "A2DD.h"

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

52
Пам’ятайте, що якщо ви виконуєте програмування шаблонів, вам потрібно зберегти все у.
linello

2
у вас є #ifndefречі в заголовку?
Ferenc Deak

4
Отже, це означає, що всі файли, що містять ваш заголовок, будуть "бачити" приватних членів. Якщо, наприклад, ви хочете опублікувати lib та його заголовок, ви повинні показати приватних членів класу?
Готьє

1
Ні, існує чудова ідіома приватної реалізації: en.wikipedia.org/wiki/Opaque_pointer Ви можете використовувати її, щоб приховати деталі реалізації.
Ferenc Deak

3
Незначна нитка з формулюванням: "Декларація класу переходить у файл заголовка". Це справді декларація, але це також визначення, але оскільки остання включає перше, я б краще сказати, що визначення класу переходить у файл заголовка. У блоці перекладу ви маєте визначення функцій-членів, а не визначення класу. Я погоджуюся, це може бути вартим невеликого редагування?
лубгр

17

Загалом, ваш .h містить визначення класу, яке представляє всі ваші дані та всі ваші декларації методу. Так у вашому випадку:

A2DD.h:

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y);    
  int getSum();
};

І тоді ваш .cpp містить реалізацію таких методів:

A2DD.cpp:

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

7

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

Отже, із прикладу прийнятої відповіді потрібна лише ця частина:

#ifndef MYHEADER_H
#define MYHEADER_H

//Class goes here, full declaration AND implementation

#endif

Визначення препроцесора #ifndef etc. дозволяють використовувати його кілька разів.

PS. Тема стає зрозумілішою, як тільки ви зрозумієте, що C / C ++ є "німим", а #include - це лише спосіб сказати "скинути цей текст у цьому місці".


Ви могли б це зробити, помістивши "розділені" файли .cpp, або це .hсправді "добре" для цього методу організації коду?
Бенні Джобіган

1
Я подумав, що деякі проекти розділяють заголовки та (єдині) файли реалізації, щоб вони могли легко поширювати файли заголовків, не розкриваючи вихідний код реалізацій.
Карл Г

Я так радий, що ти це вказав, тому що я спочатку вчився на C ++, а потім перейшов на C # багато років тому, а останнім часом знову робив багато C ++, і я забув, наскільки нудно і дратівливо розбивати файли, і я просто почав вводити все Я шукав навколо себе, шукаючи когось, що дає вагомі причини НЕ робити цього, коли знайшов це. @CarlG має хороший момент, але, крім цього сценарію, я думаю, що робити все це в Inline - це шлях.
Пітер Мур

6

В основному модифікований синтаксис оголошення / визначення функції:

a2dd.h

class A2DD
{
private:
  int gx;
  int gy;

public:
  A2DD(int x,int y);

  int getSum();
};

a2dd.cpp

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

5

A2DD.h

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y);

  int getSum();
};

A2DD.cpp

  A2DD::A2DD(int x,int y)
  {
    gx = x;
    gy = y;
  }

  int A2DD::getSum()
  {
    return gx + gy;
  }

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

А крім цього, ви можете включати в реалізацію інші заголовні файли замість заголовка. Це важливо, оскільки незалежно від того, який заголовок буде включений у ваш заголовок, він буде включений (успадкований) у будь-який інший файл, що включає файл заголовка.


4

Ви залишаєте декларації у файлі заголовка:

class A2DD
{
  private:
  int gx;
  int gy;

  public:
    A2DD(int x,int y); // leave the declarations here
    int getSum();
};

І помістіть визначення у файл реалізації.

A2DD::A2DD(int x,int y) // prefix the definitions with the class name
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

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

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


1
Якщо розміщення приватних членів і функцій у файлі заголовка не вважається протікаючими деталями реалізації?
Джейсон

1
@Jason, начебто. Це необхідні деталі реалізації. Наприклад, я повинен знати, скільки місця буде займати клас на стеці. Реалізація функції не потрібна для інших підрозділів компіляції.
Пол Дрейпер

1

Зазвичай ви ставите лише декларації та дійсно короткі вбудовані функції у файл заголовка:

Наприклад:

class A {
 public:
  A(); // only declaration in the .h unless only a short initialization list is used.

  inline int GetA() const {
    return a_;
  }

  void DoSomethingCoplex(); // only declaration
  private:
   int a_;
 };

0

Я не буду називати ваш приклад занадто простим для загальної відповіді (наприклад, він не містить шаблонних функцій, які змушують вас реалізовувати їх у заголовку), що я слідую, як правило, pimpl ідіома

Це має певні переваги, оскільки ви отримуєте швидший час складання та синтаксичний цукор:

class->member замість class.member

Єдиний недолік - додатковий покажчик, який ви платите.

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