Sposoby organizacji interfejsu i implementacji w C ++

12

Widziałem, że w C ++ istnieje kilka różnych paradygmatów dotyczących tego, co wchodzi do pliku nagłówkowego, a co do pliku CPP. AFAIK, większość ludzi, szczególnie tych z pochodzenia C, wykonuje:

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

Jednak moi wykładowcy zwykle uczą języka C ++ dla początkujących:

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

Pochodzę z Javy i zawsze trzymałem się tej drugiej drogi z kilku powodów, takich jak to, że muszę coś zmienić tylko w jednym miejscu, jeśli zmieni się nazwa interfejsu lub metody, że podoba mi się różne wcięcie rzeczy w klasach, gdy spójrz na ich implementację i że uważam, że nazwy są bardziej czytelne w fooporównaniu do foo::foo.

Chcę zbierać argumenty za i przeciw dla obu stron. Może są jeszcze inne sposoby?

Wadą mojego sposobu jest oczywiście potrzeba okazjonalnych deklaracji forward.

Felix Dombek
źródło
2
foo.cppteraz nie ma nic wspólnego z twoją fooklasą i powinno pozostać puste (być może po #includeto, aby twój system budowania był szczęśliwy).
Benjamin Bannier
2
Twoi wykładowcy są szaleni.
Wyścigi lekkości na orbicie

Odpowiedzi:

16

Podczas gdy druga wersja jest łatwiejsza do napisania, łączy interfejs z implementacją.

Pliki źródłowe zawierające pliki nagłówkowe należy rekompilować za każdym razem, gdy pliki nagłówkowe są zmieniane. W pierwszej wersji plik nagłówka byłby zmieniany tylko wtedy, gdy trzeba zmienić interfejs. W drugiej wersji zmieniłbyś plik nagłówka, jeśli potrzebujesz zmienić interfejs lub implementację.

Poza tym nie powinieneś ujawniać szczegółów implementacji , otrzymasz niepotrzebną rekompilację z drugą wersją.

LennyProgrammers
źródło
1
+1 Mój profiler nie instrumentuje kodu umieszczonego w plikach nagłówkowych - to też jest cenny powód.
Eugene
Jeśli zobaczysz moją odpowiedź na to pytanie programmers.stackexchange.com/questions/4573/… , zobaczysz, jak bardzo zależy to od semantyki klasy, tj. Od tego, co będzie z niej korzystać (w szczególności, jeśli jest to ujawniona część interfejs użytkownika i ile innych klas w systemie używa go bezpośrednio).
CashCow
3

Zrobiłem to drugi raz w latach 93–95. Minęło kilka minut, aby ponownie skompilować małą aplikację z 5-10 funkcjami / plikami (na tym samym komputerze 486 .. i nie, nie wiedziałem też o klasach, miałem zaledwie 14-15 lat i nie było internetu ) .

Tak więc to, czego uczysz początkujących i czego używasz zawodowo, to bardzo różne techniki, szczególnie w C ++.

Myślę, że porównanie między C ++ i bolidem F1 jest trafne. Nie umieszczasz początkujących w samochodzie F1 (który nawet się nie uruchomi, dopóki nie podgrzejesz silnika do 80-95 stopni Celsjusza).

Nie ucz C ++ jako pierwszego języka. Powinieneś być wystarczająco doświadczony, aby wiedzieć, dlaczego opcja 2 jest gorsza niż opcja 1 w ogóle, wiedzieć trochę, co oznacza kompilacja statyczna / linkowanie, a tym samym zrozumieć, dlaczego C ++ preferuje ją po raz pierwszy.

Macke
źródło
Ta odpowiedź byłaby jeszcze lepsza, gdybyś trochę rozwinął kompilację / linkowanie statyczne (wtedy nie wiedziałem!)
Felix Dombek
2

Druga metoda jest tym, co nazwałbym klasą całkowicie inline. Piszesz definicję klasy, ale cały kod, który z niej korzysta, po prostu wstawi kod.

Tak, kompilator decyduje, kiedy inline, a kiedy nie ... W tym przypadku pomagasz kompilatorowi w podjęciu decyzji i potencjalnie skończy się to generowaniem mniejszego kodu i potencjalnie szybszym.

Ta przewaga prawdopodobnie przeważy nad faktem, że jeśli zmodyfikujesz implementację funkcji, musisz odbudować wszystkie źródła, które z niej korzystają. W lekkiej naturze klasy nie będziesz modyfikował implementacji. Jeśli dodasz nową metodę, i tak będziesz musiał zmodyfikować nagłówek.

Ponieważ Twoja klasa staje się coraz bardziej złożona, nawet dodając pętlę, korzyść z robienia tego w ten sposób spada.

Nadal ma swoje zalety, w szczególności:

  • Jeśli jest to kod „wspólnej funkcjonalności”, możesz po prostu dołączyć nagłówek i używać go z wielu projektów bez konieczności łączenia się z biblioteką zawierającą jego źródło.

Minusem inliningu staje się problem, gdy oznacza to, że musisz wprowadzić specyfikacje implementacji do swojego nagłówka, tj. Musisz zacząć włączać dodatkowe nagłówki.

Pamiętaj, że szablony są szczególnym przypadkiem, ponieważ prawie musisz podać szczegóły implementacji. Możesz ukryć go w innym pliku, ale musi tam być. (Istnieje wyjątek od tej reguły z instancjami, ale ogólnie wstawiasz szablony).

Dojną krową
źródło
1

Może to nie być znaczące lub prawdziwe, jeśli plik wykonywalny staje się większy, ale więcej kodu w plikach nagłówkowych pozwala kompilatorowi na zoptymalizowanie szybkości.

Jeśli decydujesz, czy napisać bibliotekę tylko nagłówkową , ten temat jest tylko jednym z twoich problemów.

davidvandebunte
źródło