Cara yang benar untuk menentukan metode namespace C ++ di file .cpp

108

Mungkin duplikat, tapi tidak mudah untuk mencari ...

Diberikan header seperti:

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

Saya telah melihat method()didefinisikan dalam beberapa cara di file .cpp:

Versi 1:

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

Versi 2:

using namespace ns1;

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

Versi 3:

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

Apakah ada cara yang 'benar' untuk melakukannya? Apakah ada di antara yang 'salah' ini karena tidak semuanya memiliki arti yang sama?

Mr Boy
sumber
Di sebagian besar kode saya biasanya melihat versi ketiga (tapi saya tidak mengapa: D), versi kedua sangat berlawanan dengan mengapa ruang nama diperkenalkan, saya kira.
Tn. Anubis
1
Faktanya, alat refactoring Visual Assist dengan senang hati menghasilkan kode menggunakan # 1 atau # 3, tergantung gaya mana yang sudah digunakan dalam file target.
Tn. Boy

Jawaban:

51

Versi 2 tidak jelas dan tidak mudah dipahami karena Anda tidak tahu namespace MyClassmilik mana dan itu hanya tidak logis (fungsi kelas tidak dalam namespace yang sama?)

Versi 1 benar karena ini menunjukkan bahwa di namespace, Anda mendefinisikan fungsi.

Versi 3 juga benar karena Anda menggunakan ::operator resolusi cakupan untuk merujuk ke MyClass::method ()dalam namespace ns1. Saya lebih suka versi 3.

Lihat Namespaces (C ++) . Ini adalah cara terbaik untuk melakukannya.

GILGAMESH
sumber
22
Menyebut # 2 "salah" adalah hal yang berlebihan. Dengan logika ini, semua nama simbol adalah "salah" karena berpotensi menyembunyikan nama simbol lain di lingkup lain.
sepuluh empat
Itu tidak masuk akal. Ini akan mengkompilasi dengan baik (maaf, salah dijelaskan dalam jawaban), tetapi mengapa Anda mendefinisikan fungsi di luar namespace itu? Itu membingungkan pembaca. Selain itu, ketika banyak ruang nama digunakan, itu tidak menunjukkan ruang nama Kelas Saya milik. Versi 1 & 3 memperbaiki masalah ini. Kesimpulannya, itu tidak salah, tapi tidak jelas dan membingungkan.
GILGAMESH
3
Saya setuju dengan @PhoenicaMacia, trik penggunaan sangat buruk dan dapat menyebabkan kebingungan. Pertimbangkan kelas yang mengimplementasikan operator sebagai fungsi gratis, di header yang Anda miliki namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }, sekarang di file cpp dengan pernyataan menggunakan Anda dapat mendefinisikan fungsi anggota sebagai void X::f() {}, tetapi jika Anda mendefinisikan X operator==(X const&, X const&)Anda akan mendefinisikan operator yang berbeda dari yang satu didefinisikan di tajuk (Anda harus menggunakan 1 atau 3 untuk fungsi gratis di sana).
David Rodríguez - dribeas
1
Secara khusus saya lebih suka 1, dan contoh dalam artikel tertaut tidak benar-benar menyelesaikan apa pun yang digunakan contoh pertama 1, yang kedua menggunakan campuran 1 dan 3 (fungsi ditentukan dengan kualifikasi, tetapi ditentukan di dalam ruang nama luar)
David Rodríguez - dribeas
1
Dari 3 saya akan mengatakan 1) adalah yang terbaik namun sebagian besar IDE memiliki kebiasaan yang agak menjengkelkan untuk memasukkan semua yang ada di dalam deklarasi namespace itu dan itu menambah kebingungan.
locka
28

5 tahun kemudian dan saya pikir saya akan menyebutkan ini, yang keduanya terlihat bagus dan tidak jahat

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Puzomor Kroasia
sumber
3
Ini jawaban terbaik. Itu terlihat paling bersih, dan menghindari masalah dengan OP Versi 1, yang dapat membawa sesuatu ke dalam namespace secara tidak sengaja, dan 2, yang dapat membawa sesuatu ke ruang global secara tidak sengaja.
ayane_m
Ya, ini adalah kombinasi yang bagus dari mengetik kurang dari 3, sambil tetap menyatakan maksud secara eksplisit.
jb
14

Saya menggunakan versi 4 (di bawah) karena menggabungkan sebagian besar keuntungan dari versi 1 (ketegasan definisi resoective) dan versi 3 (harus secara maksimal eksplisit). Kerugian utama adalah bahwa orang tidak terbiasa tetapi karena saya menganggapnya secara teknis lebih unggul daripada alternatif, saya tidak keberatan.

Versi 4: gunakan kualifikasi penuh menggunakan alias namespace:

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

Di dunia saya, saya sering menggunakan alias namespace karena semuanya secara eksplisit memenuhi syarat - kecuali tidak bisa (misalnya nama variabel) atau itu adalah titik kustomisasi yang dikenal (misalnya swap () dalam template fungsi).

Dietmar Kühl
sumber
1
Sementara saya pikir logika "lebih baik jadi saya tidak peduli jika membingungkan orang" adalah logika yang salah, saya harus setuju ini adalah pendekatan yang baik untuk namespace bertingkat.
Tn. Boy
1
1 untuk gagasan "mengapa-tidak-saya-memikirkan-itu" yang luar biasa! (Adapun "orang tidak terbiasa dengan [hal baru yang secara teknis lebih unggul]", mereka akan terbiasa jika lebih banyak orang melakukannya.)
wjl
Hanya untuk memastikan saya mengerti, keduanya outerdan innerdidefinisikan sebagai ruang nama di file header lain sudah?
dekuShrub
4

Versi 3 membuat asosiasi antara kelas dan namespace sangat eksplisit dengan mengorbankan lebih banyak pengetikan. Versi 1 menghindari ini tetapi menangkap asosiasi dengan blok. Versi 2 cenderung menyembunyikan ini jadi saya akan menghindari yang itu.

Paul Joireman
sumber
3

Saya memilih Num.3 (alias versi verbose). Ini lebih banyak mengetik, tetapi maksudnya tepat untuk Anda dan kompilator. Masalah yang Anda posting apa adanya sebenarnya lebih sederhana daripada di dunia nyata. Di dunia nyata, ada cakupan lain untuk definisi, bukan hanya anggota kelas. Definisi Anda tidak terlalu rumit hanya dengan kelas - karena cakupannya tidak pernah dibuka kembali (tidak seperti ruang nama, cakupan global, dll.).

Num.1 ini bisa gagal dengan cakupan selain kelas - apapun yang bisa dibuka kembali. Jadi, Anda dapat mendeklarasikan fungsi baru di namespace menggunakan pendekatan ini, atau inline Anda bisa diganti melalui ODR. Anda akan membutuhkan ini untuk beberapa definisi (khususnya, spesialisasi template).

Num.2 Ini sangat rapuh, terutama dalam basis kode yang besar - saat header dan dependensi bergeser, program Anda akan gagal untuk dikompilasi.

Nomor 3 Ini ideal, tetapi banyak yang harus diketik - apa maksud Anda untuk mendefinisikan sesuatu . Ini persis seperti itu, dan kompilator mulai memastikan Anda tidak membuat kesalahan, definisi tidak selaras dengan deklarasinya, dll ..

justin
sumber
3

Ternyata itu bukan hanya "masalah gaya pengkodean". Num. 2 menyebabkan kesalahan penautan saat menentukan dan menginisialisasi variabel yang dideklarasikan secara eksternal di file header. Lihat contoh dalam pertanyaan saya. Definisi konstanta dalam namespace di file cpp

jakumate
sumber
2

Semua cara sudah benar, dan masing-masing memiliki kelebihan dan kekurangan.

Di versi 1, Anda memiliki keuntungan karena tidak harus menulis namespace di depan setiap fungsi. Kerugiannya adalah Anda akan mendapatkan identitas yang membosankan, terutama jika Anda memiliki lebih dari satu tingkat namespace.

Di versi 2, Anda membuat kode Anda lebih bersih, tetapi jika Anda memiliki lebih dari satu namespace yang diimplementasikan di CPP, seseorang dapat mengakses fungsi dan variabel lainnya secara langsung, membuat namespace Anda tidak berguna (untuk file cpp itu).

Di versi 3, Anda harus mengetik lebih banyak dan garis fungsi Anda mungkin lebih besar dari layar, yang berdampak buruk untuk efek desain.

Ada juga cara lain yang digunakan sebagian orang. Ini mirip dengan versi pertama, tetapi tanpa masalah identifikasi.

Seperti ini:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

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

CLOSE_NS1

Terserah Anda untuk memilih mana yang lebih baik untuk setiap situasi =]

Renan Greinert
sumber
14
Saya tidak melihat alasan apa pun untuk menggunakan makro di sini. Jika Anda tidak ingin membuat indentasi, jangan lakukan indentasi. Menggunakan makro hanya membuat kodenya menjadi kurang jelas.
tenfour
1
Saya rasa versi terakhir yang Anda sebutkan berguna setiap kali Anda ingin mengkompilasi kode Anda dengan kompiler lama yang tidak mendukung ruang nama (Ya, beberapa dinosaurus masih ada). Dalam hal ini, Anda dapat meletakkan makro di dalam #ifdefklausa.
Luca Martini
Anda tidak perlu mengidentifikasi jika tidak ingin, tetapi jika Anda tidak menggunakan makro, beberapa IDE akan mencoba melakukannya untuk Anda. Misalnya, di Visual Studio Anda dapat memilih seluruh kode dan tekan ALT + F8 untuk auto-ident. Jika Anda tidak menggunakan definisinya, Anda akan kehilangan fungsionalitas itu. Juga, saya tidak berpikir bahwa OPEN_ (namespace) dan CLOSE_ (namespace) kurang jelas, jika Anda memilikinya dalam standar pengkodean Anda. Alasan @LucaMartini memberi juga menarik.
Renan Greinert
Kalau ini dibuat generik, #define OPEN_NS(X)saya kira sedikit berguna, tetapi tidak terlalu ... Saya tidak keberatan dengan makro tetapi ini tampaknya sedikit OTT. Saya pikir pendekatan Dietmar Kühl lebih baik untuk ruang nama bersarang.
Mr Boy