C++ i implementacja metod poza deklaracją klasy w pliku CPP

0

Kolejne pytanie z cyklu "dziwnych" ;)

Sytuacja następująca:
tworzę osobny plik nazwany klasa.cpp, który chcę sobie później gdzieś include'ować.
W pliku mam stworzoną przykładową klasę:

class gucio {
  public:
    void dupa();
};

void gucio::dupa() {
}

Postępuję zgodnie z informacjami znalezionymi w necie (pierwszy przykład z brzegu - https://cpp0x.pl/kursy/Kurs-C++/Dodatkowe-materialy/Funkcje-w-klasie-czyli-metody/312) - teoretycznie jest możliwe definiowanie treści metody poza klasą. Ale odpalając w/w kod, dostaję następujący błąd podczas kompilacji:

klasa.cpp multiple definition of `gucio::dupa()'
[...]
klasa.cpp first defined here

Można to obejść przez tworzenie plików nagłówkowych, ale wolałbym tego uniknąć.

Stąd 2 pytania:

  1. czy można (jeśli tak - to w jaki sposób) tworzyć w pliku CPP najpierw samą strukturę klasy, a później/poniżej wpisać ciała metod? Wiem, że można metody napisać wprost w ciele klasy, ale takie rozwiązanie mi się nie podoba, bo robi się jeden długi ciąg trudny do przeglądania. A ja wolę mieć najpierw definicję samej klasy, a dopiero poniżej treść metod. Czy da się tak to zrobić (bez rozbijania na pliki .h i .cpp)?

  2. Jeśli jednak nie da się uniknąć tego rozbicia - czy są jakieś sposoby/pluginy do VSCode, które jakoś automatyzują/ułatwiają pracę z tymi 2 plikami jednocześnie? W sensie - tworzę/zmieniam coś w pliku CPP i zmiana się nanosi także na odpowiedni plik H (oraz odwrotnie). Na razie ma to ręcznie zsynchronizowane, co jest trochę niefajne - muszę zmiany nanosić dwa razy, w każdym z plików osobno, nie są one z sobą w żaden sposób połączone/zsynchronizowane.

3

Do VScode nie widziałem takiego pluginu, trochę mi tego brakuje, ale inne IDE jak najbardziej to mają (np. Qt Creator, albo CLion)

Plik nagłówkowy to właściwie konieczność; pomijając moduły z C++20, to jedyna opcja na dzielenie kodu w C++.

3

Można to obejść przez tworzenie plików nagłówkowych, ale wolałbym tego uniknąć.

Pliki nagłówkowe to esencja c++ :)
Przy okazji poczytaj sobie o guardach w headerach lub pragma once

4

Jak chcesz nie postępować zgodnie ze sztuką, to jest coś co możesz zrobić, a mianowicie dodać specyfikator inline do wszystkich metod - jeśli definicja metody znajduje się w ciele klasy to inline jest "implied".

0

@recovery: czy z tymi guardami chodzi o coś w stylu poniższego? Jeśli tak to wiem i stosuję, ale i tak dzięki za przypomnienie :)

#ifndef NAZWA_MODULU
#define NAZWA_MODULU
 
// tutaj dajemy treść włączanego pliku
 
#endif

@enedil - tak, widziałem gdzieś informację o deklaracji inline. Pytanie - czy nie ma to żadnych skutków ubocznych? Nic złego się przy okazji nie stanie, mogę tego korzystać bezkarnie?

Poza tym - pytanie do wszystkich: w pierwszym poście podałem link do jakiegoś przykładowego kursu. Jest tam napisane, że można deklarować metody poza ciałem klasy. Ale z tego co piszecie - takie coś nie ma prawa działać. W takim razie - o co chodzi? Czemu koleś podaje sposób podawania treści metod w oddzieleniu od deklaracji klasy, ale to nie działa jak sobie wpisałem i (bazując na Waszych odpowiedziach) nie powinno zatrybić.

2

@cerrato: kq już się odniósł do inline. Poza tym, o ile mi wiadomo, brak inline to jedyna różnica definicji funkcji poza ciałem klasy.

Natomiast co do ogólnego pytania - masz na myśli definiowanie poza ciałem, a nie deklarowanie, tak? W każdym razie, możesz to robić, ale nie jeśli decydujesz się includować takie pliki .cpp - includowanie to po prostu wklejenie zawartości pliku. Jeśli przypadkiem, w wyniku preprocesora, w jednym pliku znajdą się dwie definicje funkcji, to będzie błąd kompilacji. Jeśli w dwóch osobnych plikach (po preprocesowaniu) znajdą się definicje tej samej funkcji, to będzie błąd na etapie linkowania. Jeśli jednak będzie tylko jedna definicja (np. w pliku .cpp który nigdzie nie jest includowany), to nie występuje żadnen z tych problemów.

Nieincludowanie plików .cpp umożliwia też szybszą kompilację inkrementalną.

1

A na marginesie, co na stare lata Cię wzięło na język, w którym trzeba popełnić 2-3x więcej linii, niż w jakimkolwiek innym, można wdepnąć w UB chodzące stadami itd, który w pełni pojmują nieliczni czempioni ?

Dzieciaki od neostrady odpowiadają *"bo C++ jest najszybsze" * ale chyba nie ty?

4

@cerrato Wszystko się da. Twoja klasa powinna wyglądać w ten sposób

// plik add_1.cpp

  #ifndef add_1_cpp_
  #define add_1_cpp_
  
  #include <iostream>
  
  struct TypAdd_1
  {
    void cout() const;
  };
  
  #ifdef add_1_impl
  #undef add_1_impl
  
  void TypAdd_1::cout() const
 {
   std::cout << "TypeAdd_1 cout\n";
 }
 
 #endif
 
 #endif

I wtedy **w jednym ** miejscu inkludujesz ją tak

#define add_1_impl
#include "add_1.cpp

a potem w każdym innym klasycznie

#include "add_1.cpp

EDIT
Demonstracja https://wandbox.org/permlink/ryOOYsYo9CcX7gF8 Dwie klasy dziedziczące po sobie a potem obie jeszcze raz zainkludowane w main, żadnych nagłówków, żadnych inline.

0

@cerrato:

Nie umiem sobie "reverse" odtworzyć, jakie masz cele

Konwencji to jakościowego kodu "recznego" C++ jest dużo, są ugruntowane, wystarczy przyjąć i przestrzegać - ale ty nie kombinujesz przypadkiem kodu generować, albo coś podobnego ?

2
several napisał(a):

@cerrato Wszystko się da. Twoja klasa powinna wyglądać w ten sposób

Fajnie, tylko w przypadku zmian dotykających implementacji, doprowadzi to do kaskady rekompilacji, co strasznie obniży komfort pracy.
Na dodatek, takie niespodziewane udziwnienia mają tendencję do tworzenia problemów, z którymi przyszły mainteiner będzie się musiał zmierzyć.

0

@AnyKtokolwiek: nie do końca rozumiem o co pytasz, więc może moja odpowiedź nie będzie dla Ciebie zadowalająca ;)

W dużym uproszczeniu - staram się uniknąć trzymania kodu w pliku cpp oraz h, bo to jest w moim przypadku niepotrzebnym utrudnianiem życia. Wiem, że jest ryzyko (o czym wspomniał przed chwilą @MarekR22) dodakowej niepotrzebnej kompilacji - ale w moim przypadku to jest dosłownie parę sekund różnicy, projekt jest malutki, więc nie robi mi to żadnej różnicy.

Chodzi mi o to, żeby mieć o wiele wygodniejszy/czytelniejszy kod - coś na zasadzie:

// NIEFAJNE

class COS {
 void raz () {
  //blablabla
  //blabla2
  //blabla3
};

void dwa () {
  //bububu1
  //bububu2
  //bubu3
}
}

// LEPIEJ

class COS {
  void raz
  void dwa
}

void COS::raz {
  //blablabla
  //blabla2
  //blabla3
};

void COS::dwa () {
  //bububu1
  //bububu2
  //bubu3
}

Może to kwestia przyzwyczajeń z Pascala, ale drugi zapis wydaje mi się o wiele czytelniejszy. Najpierw mamy deklarację samej klasy - jedynie widzimy co w niej się zawiera, taki jakby spis treści - każda linia to osobna metoda czy zmienna/stała. Rzucasz okiem i od razu widzisz, co z klasą można zrobić i co ona oferuje. Patrząc w ten sposób - nie interesuje mnie impementacja - dlatego wywalamy ją poza klasę. Niestety - jak pisałem, w C jest rozbicie na pliki nagłówkowe i z kodem - co dla mnie jest bezsensowne, dlatego chciałem uzyskać pokazany powyżej efekt.

takie niespodziewane udziwnienia mają tendencję do tworzenia problemów, z którymi przyszły mainteiner będzie się musiał zmierzyć

Tak, ale pamiętajcie chłopaki, że tutaj jest tylko jeden maintainer, kod na użytek własny, nigdzie publikowany nie będzie - więc tym się przejmować nie trzeba. Ale co do zasady - masz pełną rację, w komercyjnych lub grupowych projektach powinno się trzymać jakichś ustalonych zasad itp.

1
MarekR22 napisał(a):
several napisał(a):

@cerrato Wszystko się da. Twoja klasa powinna wyglądać w ten sposób

Fajnie, tylko w przypadku zmian dotykających implementacji, doprowadzi to do kaskady rekompilacji, co strasznie obniży komfort pracy.
Na dodatek, takie niespodziewane udziwnienia mają tendencję do tworzenia problemów, z którymi przyszły mainteiner będzie się musiał zmierzyć.

No tak, ale to głównie wynika z natury problemu postawionego przez @cerrato, czyli wszystko w .cpp. Czegokolwiek byś tu nie wymyślił to będziesz rekompilować więcej plików niż przy rozsądnym rozgraniczeniu nagłówek - implementacja. Ja tylko pokazałem jakbym to zrobił, ocenę czy to dobry pomysł zostawiam dla siebie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1