Rozdzielanie kodu klasy na nagłówek i plik CPP


169

Nie wiem, jak oddzielić kod implementacji i deklaracji prostej klasy do nowego nagłówka i pliku CPP. Na przykład, jak oddzielić kod dla następującej klasy?

class A2DD
{
  private:
  int gx;
  int gy;

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

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

12
Tylko kilka komentarzy: Konstruktor powinien zawsze używać listy inicjalizacyjnej zamiast ustawiać elementy członkowskie w treści. Dobre i proste wyjaśnienie można znaleźć na stronie: codeguru.com/forum/showthread.php?t=464084 Jest też, przynajmniej w większości miejsc, zwyczaj umieszczania pola publicznego na górze. Nie wpłynie to na nic, ale ponieważ pola publiczne są dokumentacją twojej klasy, warto umieścić je na górze.
martiert

2
@martiert Posiadanie public:członków na górze może mieć duży wpływ , jeśli użytkownik przeniósł ich zgodnie z tą radą - ale miał zależności porządkowe między członkami i nie był jeszcze świadomy, że członkowie są inicjowani w kolejności ich deklaracji ;-)
underscore_d

1
@underscore_d to prawda. Ale z drugiej strony, wszyscy kompilujemy z ostrzeżeniami jako błędami i wszystkimi ostrzeżeniami, jakie przychodzą nam do głowy, prawda? To by przynajmniej powiedziało ci, że to schrzanisz, ale tak, ludzie używają niewielkich ostrzeżeń i po prostu je ignorują :(
martiert

@martiert Dobra uwaga, trochę zapomniałem, że generuje ostrzeżenia - gdyby tylko ostrzeżenia były czytane przez większość :-) Używam ich i próbuję je wszystkie zakodować. Kilka jest nieuniknionych - więc mówię „dzięki za ostrzeżenie, ale wiem, co robię!”. - ale większość z nich najlepiej naprawić, aby później uniknąć nieporozumień.
underscore_d

Posiadanie pól publicznych na szczycie to tylko styl, który niestety moim zdaniem przyjęło zbyt wielu. Dodatkowo musisz pamiętać o kilku rzeczach, o których wspomniał @martiert.
Vassilis

Odpowiedzi:


232

Deklaracja klasy trafia do pliku nagłówkowego. Ważne jest, aby dodać #ifndefosłony włączające, a jeśli jesteś na platformie MS, możesz również użyć #pragma once. Pominąłem również prywatne, domyślnie członkowie klasy C ++ są prywatni.

// A2DD.h
#ifndef A2DD_H
#define A2DD_H

class A2DD
{
  int gx;
  int gy;

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

};

#endif

a implementacja idzie w pliku CPP:

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

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

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

52
Pamiętaj, że jeśli robisz programowanie szablonów, to musisz zachować wszystko w pliku .h, aby kompilator utworzył instancję odpowiedniego kodu w momencie kompilacji.
linello

2
czy masz #ifndefrzeczy w nagłówku?
Ferenc Deak

4
Oznacza to, że wszystkie pliki, które zawierają Twój plik nagłówkowy, „zobaczą” członków prywatnych. Jeśli na przykład chcesz opublikować bibliotekę i jej nagłówek, musisz pokazać prywatnych członków klasy?
Gauthier

1
Nie, istnieje wspaniały idiom implementacji prywatnej: en.wikipedia.org/wiki/Opaque_pointer Możesz go użyć do ukrycia szczegółów implementacji.
Ferenc Deak

3
Drobny chwytak z napisem: „Deklaracja klasy trafia do pliku nagłówkowego”. To jest rzeczywiście deklaracja, ale jest to także definicja, ale ponieważ ta druga zawiera poprzednią, wolałbym powiedzieć, że definicja klasy trafia do pliku nagłówkowego. W jednostce tłumaczeniowej masz definicję funkcji składowych, a nie definicję klasy. Zgadzasz się, to może być warte niewielkiej zmiany?
lubgr

17

Ogólnie twój .h zawiera definicję klasy, czyli wszystkie twoje dane i wszystkie deklaracje metod. Tak jak w twoim przypadku:

A2DD.h:

class A2DD
{
  private:
  int gx;
  int gy;

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

A następnie twój .cpp zawiera implementacje metod takich jak ta:

A2DD.cpp:

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

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

7

Należy zwrócić uwagę czytelników, którzy natkną się na to pytanie podczas szerszego badania tematu, że procedura zaakceptowanej odpowiedzi nie jest wymagana w przypadku, gdy chcesz po prostu podzielić projekt na pliki. Jest potrzebny tylko wtedy, gdy potrzebujesz wielu implementacji pojedynczych klas. Jeśli Twoja implementacja na klasę to jedna, wystarczy jeden plik nagłówkowy dla każdej.

Stąd z przykładu przyjętej odpowiedzi potrzebna jest tylko ta część:

#ifndef MYHEADER_H
#define MYHEADER_H

//Class goes here, full declaration AND implementation

#endif

Definicje preprocesorów #ifndef itp. Pozwalają na wielokrotne ich użycie.

PS. Temat staje się jaśniejszy, gdy zdasz sobie sprawę, że C / C ++ jest „głupi”, a #include to tylko sposób na powiedzenie „zrzuć ten tekst w tym miejscu”.


czy mógłbyś to zrobić, umieszczając „podzielone” pliki .cpp, czy jest .hnaprawdę „dobre” tylko dla tej metody organizacji kodu?
Benny Jobigan

1
Pomyślałem, że niektóre projekty dzielą pliki nagłówkowe i (pojedyncze) pliki implementacji, aby mogły łatwo dystrybuować pliki nagłówkowe bez ujawniania kodu źródłowego implementacji.
Carl G

Cieszę się, że zwróciłeś na to uwagę, ponieważ początkowo nauczyłem się C ++, a potem przestawiłem się na C # wiele lat temu i ostatnio znowu robiłem dużo C ++ i zapomniałem, jak żmudne i denerwujące jest dzielenie plików i po prostu zacząłem umieszczać wszystko w nagłówki. Szukałem wokół, szukając kogoś, kto podał dobre powody, aby tego NIE robić, kiedy to znalazłem. @CarlG ma dobry punkt widzenia, ale poza tym scenariuszem myślę, że robienie tego wszystkiego w trybie inline jest drogą do zrobienia.
Peter Moore,

6

Zasadniczo zmodyfikowana składnia deklaracji / definicji funkcji:

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;
  }

Pomysł polega na zachowaniu wszystkich sygnatur funkcji i elementów członkowskich w pliku nagłówkowym.
Pozwoli to innym plikom projektu zobaczyć, jak wygląda klasa, bez konieczności znajomości implementacji.

Poza tym możesz następnie dołączyć inne pliki nagłówkowe do implementacji zamiast nagłówka. Jest to ważne, ponieważ niezależnie od tego, które nagłówki są zawarte w pliku nagłówkowym, zostaną uwzględnione (odziedziczone) w każdym innym pliku zawierającym plik nagłówkowy.


4

Pozostawiasz deklaracje w pliku nagłówkowym:

class A2DD
{
  private:
  int gx;
  int gy;

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

I umieść definicje w pliku implementacji.

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

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

Możesz połączyć te dwa elementy (odejdź getSum() na przykład definicję w nagłówku). Jest to przydatne, ponieważ daje kompilatorowi większą szansę na przykład na wstawianie. Ale oznacza to również, że zmiana implementacji (jeśli pozostawiona w nagłówku) może spowodować przebudowę wszystkich innych plików, które zawierają nagłówek.

Zauważ, że w przypadku szablonów musisz zachować to wszystko w nagłówkach.


1
Umieszczanie prywatnych członków i funkcji w pliku nagłówkowym nie jest uważane za wyciek szczegółów implementacji?
Jason

1
@Jason, w pewnym sensie. To są niezbędne szczegóły implementacji. Np. Muszę wiedzieć, ile miejsca zajmie klasa na stosie. Implementacje funkcji nie są konieczne w przypadku innych jednostek kompilacji.
Paul Draper,

1

Zwykle umieszczasz tylko deklaracje i naprawdę krótkie funkcje wbudowane w pliku nagłówkowym:

Na przykład:

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

Nie będę odnosić się zbyt swój przykład jak to jest dość proste do ogólnej odpowiedzi (na przykład nie zawiera funkcje na matrycy, które zmuszają do ich wdrożenia w nagłówku), co śledzę jak zasada jest pimpl idiom

Ma sporo zalet, ponieważ uzyskujesz szybsze czasy kompilacji i cukier syntaktyczny:

class->member zamiast class.member

Jedyną wadą jest dodatkowy wskaźnik, który płacisz.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.