Poprawny sposób definiowania metod przestrzeni nazw C ++ w pliku .cpp

108

Prawdopodobnie duplikat, ale niełatwy do wyszukania ...

Biorąc pod uwagę nagłówek taki jak:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

Widziałem method()zdefiniowane na kilka sposobów w pliku .cpp:

Wersja 1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

Wersja 2:

using namespace ns1;

void MyClass::method()
{
 ...
}

Wersja 3:

void ns1::MyClass::method()
{
 ...
}

Czy istnieje „właściwy” sposób, aby to zrobić? Czy któryś z tych „błędów” nie oznacza tego samego?

Mr. Boy
źródło
W większości kodów zwykle widzę trzecią wersję (ale nie wiem dlaczego: D), druga wersja jest po prostu przeciwna do tego, dlaczego wprowadzono przestrzenie nazw, jak sądzę.
Pan Anubis
1
Co ciekawe, narzędzia refaktoryzacji Visual Assist z przyjemnością generują kod za pomocą # 1 lub # 3, w zależności od tego, który styl jest już używany w pliku docelowym.
Mr. Boy

Odpowiedzi:

51

Wersja 2 jest niejasna i niełatwa do zrozumienia, ponieważ nie wiesz, do której przestrzeni nazw MyClassnależy i jest po prostu nielogiczna (funkcja klasy nie znajduje się w tej samej przestrzeni nazw?)

Wersja 1 ma rację, ponieważ pokazuje, że w przestrzeni nazw definiujesz funkcję.

Wersja 3 jest słuszna również dlatego, że użyłeś ::operatora rozpoznawania zakresu do odniesienia się do MyClass::method ()w przestrzeni nazw ns1. Wolę wersję 3.

Zobacz przestrzenie nazw (C ++) . To najlepszy sposób, aby to zrobić.

GILGAMESH
źródło
22
Nazywanie nr 2 „złym” to ogromna przesada. Zgodnie z tą logiką wszystkie nazwy symboli są „błędne”, ponieważ mogą potencjalnie ukrywać inne nazwy symboli w innych zakresach.
tenfour
To nielogiczne. Skompiluje się dobrze (przepraszam, źle to wyjaśniłem w odpowiedzi), ale dlaczego miałbyś definiować funkcję poza jej przestrzenią nazw? To dezorientuje czytelnika. Ponadto, gdy używanych jest wiele przestrzeni nazw, nie pokazuje, do której przestrzeni nazw należy MyClass. Wersje 1 i 3 rozwiązują ten problem. Podsumowując, nie jest to złe, ale po prostu niejasne i mylące.
GILGAMESH
3
Zgadzam się z @PhoenicaMacia, sztuczka z użyciem jest okropna i może prowadzić do nieporozumień. Rozważ klasę, która implementuje operator jako funkcję wolną, w nagłówku, który miałbyś namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }, teraz w pliku cpp z instrukcją using możesz zdefiniować funkcję składową jako void X::f() {}, ale jeśli zdefiniujesz X operator==(X const&, X const&), zdefiniujesz inny operator niż ten zdefiniowane w nagłówku (będziesz musiał użyć 1 lub 3 dla wolnej funkcji tam).
David Rodríguez - dribeas
1
W szczególności wolę 1, a przykład w powiązanym artykule tak naprawdę niczego nie rozwiązuje, pierwszy przykład używa 1, drugi używa mieszanki 1 i 3 (funkcje są zdefiniowane z kwalifikacją, ale są zdefiniowane w zewnętrznej przestrzeni nazw)
David Rodríguez - dribeas
1
Z 3, powiedziałbym, że 1) jest najlepszy, jednak większość IDE ma dość irytujący zwyczaj wcięcia wszystkiego w tej deklaracji przestrzeni nazw, co wprowadza pewne zamieszanie.
locka
28

Pięć lat później pomyślałem, że wspomnę o tym, co zarówno ładnie wygląda, jak i nie jest złe

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Puzomor Chorwacja
źródło
3
To najlepsza odpowiedź. Wygląda na najczystszy i pozwala uniknąć problemów z wersjami OP 1, które mogą w niezamierzony sposób przenosić rzeczy do przestrzeni nazw i 2, które mogą nieumyślnie przenosić rzeczy do przestrzeni globalnej.
ayane_m
Tak, to świetne połączenie mniej pisania niż 3, przy jednoczesnym wyraźnym deklarowaniu zamiaru.
jb
14

Używam wersji 4 (poniżej), ponieważ łączy ona większość zalet wersji 1 (zwięzłość definicji resoekcyjnej) i wersji 3 (maksymalnie wyraźna). Główną wadą jest to, że ludzie nie są do tego przyzwyczajeni, ale ponieważ uważam, że jest to technicznie lepsze od alternatyw, nie mam nic przeciwko.

Wersja 4: użyj pełnej kwalifikacji przy użyciu aliasów przestrzeni nazw:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

W moim świecie często używam aliasów przestrzeni nazw, ponieważ wszystko jest jawnie kwalifikowane - chyba że nie może (np. Nazwy zmiennych) lub jest to znany punkt dostosowywania (np. Swap () w szablonie funkcji).

Dietmar Kühl
źródło
1
Chociaż myślę, że logika „jest lepiej, więc nie obchodzi mnie, czy to myli ludzi” jest błędna, muszę się zgodzić, że jest to dobre podejście do zagnieżdżonych przestrzeni nazw.
Mr. Boy
1
+1 za doskonały pomysł „dlaczego nie pomyślałem o tym”! (Jeśli chodzi o „ludzie nie są przyzwyczajeni do [nowych, lepszych technicznie rzeczy]”, przyzwyczają się do tego, jeśli zrobi to więcej osób.)
wjl
Wystarczy, aby upewnić się, że rozumiem, oba outeri innerzdefiniowane jako nazw już w innych plikach nagłówkowych?
dekuShrub
4

Wersja 3 sprawia, że ​​powiązanie między klasą a przestrzenią nazw jest bardzo wyraźne, kosztem częstszego pisania. Wersja 1 unika tego, ale przechwytuje powiązanie z blokiem. Wersja 2 ma tendencję do ukrywania tego, więc unikałbym tego.

Paul Joireman
źródło
3

Wybieram Num.3 (czyli wersję pełną). Jest to bardziej typowe, ale intencja jest dokładna dla Ciebie i dla kompilatora. Problem, który opublikowałeś w obecnej postaci, jest w rzeczywistości prostszy niż w prawdziwym świecie. W prawdziwym świecie istnieją inne zakresy definicji, a nie tylko członkowie klas. Twoje definicje nie są zbyt skomplikowane w przypadku samych klas - ponieważ ich zakres nigdy nie jest ponownie otwierany (w przeciwieństwie do przestrzeni nazw, zasięgu globalnego itp.).

Num.1 może się to nie powieść w przypadku zakresów innych niż klasy - wszystko, co można ponownie otworzyć. Możesz więc zadeklarować nową funkcję w przestrzeni nazw, używając tego podejścia, lub twoje inline mogą zostać zastąpione przez ODR. Będzie to potrzebne w przypadku niektórych definicji (w szczególności specjalizacji szablonów).

Num.2 Jest to bardzo delikatne, szczególnie w dużych bazach kodu - gdy zmieniają się nagłówki i zależności, twój program nie skompiluje się.

Num.3 Jest to idealne rozwiązanie, ale dużo do pisania - co swoją intencją jest, aby zdefiniować coś . To robi dokładnie to, a kompilator włącza się, aby upewnić się, że nie popełniłeś błędu, definicja nie jest niezgodna z jej deklaracją itp.

Justin
źródło
3

Okazuje się, że to nie tylko „kwestia stylu kodowania”. Num. 2 prowadzi do błędu łączenia podczas definiowania i inicjalizacji zmiennej zadeklarowanej extern w pliku nagłówkowym. Spójrz na przykład w moim pytaniu. Definicja stałej w przestrzeni nazw w pliku cpp

jakumate
źródło
2

Wszystkie drogi są właściwe, a każda ma swoje zalety i wady.

W wersji 1 masz tę zaletę, że nie musisz pisać przestrzeni nazw przed każdą funkcją. Wadą jest to, że otrzymasz nudną identyfikację, szczególnie jeśli masz więcej niż jeden poziom przestrzeni nazw.

W wersji 2 sprawiasz, że kod jest bardziej przejrzysty, ale jeśli masz więcej niż jedną przestrzeń nazw implementowaną w CPP, jedna może mieć bezpośredni dostęp do funkcji i zmiennych drugiej, czyniąc twoją przestrzeń nazw bezużyteczną (dla tego pliku cpp).

W wersji 3 będziesz musiał wpisać więcej, a linie funkcji mogą być większe niż ekran, co jest niekorzystne dla efektów projektowych.

Jest też inny sposób, w jaki niektórzy go używają. Jest podobny do pierwszej wersji, ale bez problemów z identyfikacją.

To jest tak:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

Do Ciebie należy wybór, który jest lepszy w każdej sytuacji =]

Renan Greinert
źródło
14
Nie widzę powodu, aby używać tutaj makra. Jeśli nie chcesz tworzyć wcięć, po prostu nie wykonuj wcięć. Użycie makra sprawia, że ​​kod jest mniej oczywisty.
tenfour
1
Myślę, że ostatnia wersja, o której wspomniałeś, jest przydatna, gdy chcesz skompilować swój kod za pomocą starych kompilatorów, które nie obsługują przestrzeni nazw (tak, niektóre dinozaury wciąż są w pobliżu). W takim przypadku możesz umieścić makro wewnątrz #ifdefklauzuli.
Luca Martini
Nie musisz identyfikować, jeśli nie chcesz, ale jeśli nie używasz makr, niektóre IDE spróbują to zrobić za Ciebie. Na przykład w programie Visual Studio możesz zaznaczyć cały kod i nacisnąć klawisze ALT + F8, aby automatycznie zidentyfikować. Jeśli nie użyjesz definicji, utracisz tę funkcjonalność. Nie sądzę również, aby OPEN_ (przestrzeń nazw) i CLOSE_ (przestrzeń nazw) były mniej oczywiste, jeśli masz je w swoim standardzie kodowania. Powód podany przez @LucaMartini jest również interesujący.
Renan Greinert
Jeśli to było generyczne, to znaczy #define OPEN_NS(X)myślę, że jest to trochę przydatne, ale nie do końca ... Nie sprzeciwiam się makrom, ale wydaje się to trochę OTT. Myślę, że podejście Dietmara Kühla jest lepsze w przypadku zagnieżdżonych przestrzeni nazw.
Mr. Boy