Разделение кода класса на заголовок и файл cpp

170

Я запутался в том, как разделить код реализации и объявлений простого класса на новый заголовок и файл cpp. Например, как бы я отделил код для следующего класса?

class A2DD
{
  private:
  int gx;
  int gy;

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

  int getSum()
  {
    return gx + gy;
  }
};
drdrdr
источник
12
Просто пара комментариев: конструктор должен всегда использовать список инициализации вместо установки элементов в теле. Хорошее и простое объяснение см. По адресу : codeguru.com/forum/showthread.php?t=464084 Также, по крайней мере, в большинстве мест, общедоступное поле вверху. Это ни на что не повлияет, но поскольку открытые поля - это документация вашего класса, имеет смысл иметь это наверху.
Мартиерт
2
@martiert Наличие public:членов наверху может сильно повлиять , если пользователь переместит их в соответствии с этим советом - но имел упорядочение зависимостей между членами и еще не знал, что члены инициализируются в порядке их объявления ;-)
underscore_d
1
@underscore_d это правда. Но опять же, мы все собираем предупреждения с ошибками и все предупреждения, о которых мы можем подумать, верно? Это, по крайней мере, скажет вам, что вы все испортили, но да, люди используют маленькие предупреждения и просто игнорируют их :(
martiert
@martiert Хороший вопрос, вроде как забыл, что генерирует предупреждения - если бы большинство предупреждений было прочитано :-) Я использую их и пытаюсь все их кодировать. Некоторые неизбежны - поэтому я говорю «спасибо за предупреждение, но я знаю, что я делаю!» - но большинство лучше исправить, чтобы избежать путаницы позже.
underscore_d
Наличие публичных полей наверху - это просто стиль, который, к моему мнению, слишком многие приняли. Кроме того, вам нужно помнить некоторые вещи, как упомянуто @martiert.
Василис

Ответы:

233

Объявление класса идет в заголовочный файл. Важно, чтобы вы добавили #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;
}
Ференц Дик
источник
53
Помните, что если вы занимаетесь программированием на основе шаблонов, вам нужно хранить все в файле .h, чтобы компилятор создавал правильный код в момент компиляции.
Linello
2
у вас есть #ifndefматериал в шапке?
Ференц Дик
4
Таким образом, это означает, что все файлы, которые включают ваш заголовочный файл, будут "видеть" приватных участников Например, если вы хотите опубликовать библиотеку и ее заголовок, вам нужно показать закрытые члены класса?
Готье
1
Нет, есть замечательная идиома частной реализации: en.wikipedia.org/wiki/Opaque_pointer. Вы можете использовать ее, чтобы скрыть детали реализации.
Ференц Дик
3
Незначительная мелочь с формулировкой: «Объявление класса идет в заголовочный файл». Это действительно объявление, но это также и определение, но поскольку последнее включает в себя первое, я бы скорее сказал, что определение класса входит в заголовочный файл. В модуле перевода у вас есть определение функций-членов, а не определение класса. Я согласен, это может стоить небольшого редактирования?
Lubgr
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 и т. Д. Позволяют использовать его несколько раз.

PS. Тема становится понятнее, когда вы понимаете, что C / C ++ «тупой», а #include - это просто способ сказать «сбросить этот текст в этом месте».

J RIV
источник
Вы могли бы сделать это, поместив «разделенные» файлы .cpp, или это .hдействительно «хорошо» для этого метода организации кода?
Бенни Джобиган
1
Я думал, что некоторые проекты разделяют заголовочные и (одиночные) файлы реализации, чтобы они могли легко распространять заголовочные файлы без раскрытия исходного кода реализаций.
Карл Дж
Я так рад, что вы отметили это, потому что я первоначально изучил на C ++, а затем переключился на C # много лет назад и недавно снова много занимался C ++, и я забыл, как утомительно и раздражающе разбивать файлы, и просто начал складывать все в Заголовки. Я искал вокруг, ища кого-то, дающего веские причины НЕ делать этого, когда я нашел это. @CarlG имеет хорошую точку зрения, но, помимо этого сценария, я думаю, что делать все это в линию - путь.
Питер Мур
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
@ Джейсон, вроде. Это необходимые детали реализации. Например, я должен знать, сколько места будет занимать класс в стеке. Реализации функций не нужны для других модулей компиляции.
Пол Дрейпер
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

Единственным недостатком является дополнительный указатель, который вы платите.

Spyros Mourelatos
источник