วิธีที่ถูกต้องในการกำหนดเมธอด C ++ namespace ในไฟล์. cpp


108

อาจจะซ้ำกัน แต่ไม่ใช่เรื่องง่ายที่จะค้นหา ...

ให้ส่วนหัวเช่น:

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

ฉันเห็นว่ามีการmethod()กำหนดไว้หลายวิธีในไฟล์. cpp:

เวอร์ชัน 1:

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

เวอร์ชัน 2:

using namespace ns1;

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

เวอร์ชัน 3:

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

มีวิธีที่ 'ถูกต้อง' หรือไม่? สิ่งเหล่านี้ 'ผิด' หรือไม่ที่ไม่ได้หมายถึงสิ่งเดียวกันทั้งหมด?


ในรหัสส่วนใหญ่ฉันมักจะเห็นเวอร์ชันที่สาม (แต่ฉันไม่ทำไม: D) เวอร์ชันที่สองนั้นตรงข้ามกับเหตุผลที่ฉันคิดว่าเนมสเปซถูกนำมาใช้
Mr. Anubis

1
ตามความจริงที่น่าสนใจเครื่องมือปรับโครงสร้าง Visual Assist ยินดีที่จะสร้างโค้ดโดยใช้ # 1 หรือ # 3 ขึ้นอยู่กับสไตล์ที่ใช้อยู่แล้วในไฟล์เป้าหมาย
คุณบอย

คำตอบ:


51

เวอร์ชัน 2 ไม่ชัดเจนและเข้าใจได้ไม่ยากเพราะคุณไม่รู้ว่าMyClassเป็นของเนมสเปซใดและมันก็ไม่สมเหตุสมผล (ฟังก์ชันคลาสไม่ได้อยู่ในเนมสเปซเดียวกัน?)

เวอร์ชัน 1 ถูกต้องเพราะแสดงว่าในเนมสเปซคุณกำลังกำหนดฟังก์ชัน

3 รุ่นที่ถูกต้องยังเป็นเพราะคุณใช้::ประกอบการที่มีรายละเอียดขอบเขตในการอ้างถึงMyClass::method ()ในการ ns1namespace ฉันชอบเวอร์ชัน 3 มากกว่า

ดูNamespaces (C ++) นี่เป็นวิธีที่ดีที่สุดในการทำเช่นนี้


22
การเรียก # 2 "ผิด" เป็นการพูดเกินจริงอย่างมาก ด้วยเหตุผลนี้ชื่อสัญลักษณ์ทั้งหมดจึง "ผิด" เนื่องจากอาจซ่อนชื่อสัญลักษณ์อื่นในขอบเขตอื่นได้
tenfour

มันไร้เหตุผล มันจะคอมไพล์ได้ดี (ขออภัย misex อธิบายว่าในคำตอบ) แต่ทำไมคุณถึงกำหนดฟังก์ชันนอกเนมสเปซ มันทำให้ผู้อ่านสับสน นอกจากนี้เมื่อมีการใช้เนมสเปซจำนวนมากจะไม่แสดงว่า MyClass เป็นของเนมสเปซใด เวอร์ชัน 1 & 3 แก้ไขปัญหานี้ สรุปไม่ผิด แต่แค่ไม่ชัดเจนและสับสน
GILGAMESH

3
ฉันเห็นด้วยกับ @PhoenicaMacia การใช้กลอุบายนั้นแย่มากและอาจทำให้สับสนได้ พิจารณาคลาสที่ใช้ตัวดำเนินการเป็นฟังก์ชันอิสระในส่วนหัวที่คุณจะมีnamespace N {struct X { void f(); }; X operator==( X const &, X const & ); }ตอนนี้ในไฟล์ cpp ด้วยคำสั่งใช้คุณสามารถกำหนดฟังก์ชันสมาชิกเป็นvoid X::f() {}แต่ถ้าคุณกำหนดX operator==(X const&, X const&)คุณจะกำหนดตัวดำเนินการที่แตกต่างจากตัวดำเนินการ กำหนดไว้ในส่วนหัว (คุณจะต้องใช้ 1 หรือ 3 สำหรับฟังก์ชันฟรีที่นั่น)
David Rodríguez - น้ำลายไหล

1
โดยเฉพาะอย่างยิ่งฉันชอบ 1 และตัวอย่างในบทความที่เชื่อมโยงไม่ได้แก้ไขอะไรเลยที่ตัวอย่างแรกใช้ 1 ส่วนที่สองใช้การผสมผสานระหว่าง 1 และ 3 (ฟังก์ชันถูกกำหนดด้วยคุณสมบัติแต่ถูกกำหนดไว้ภายในเนมสเปซภายนอก)
David Rodríguez - น้ำลายไหล

1
จาก 3 ข้อฉันจะบอกว่า 1) เป็น IDE ที่ดีที่สุด แต่ส่วนใหญ่มีนิสัยที่ค่อนข้างน่ารำคาญในการเยื้องทุกสิ่งภายในการประกาศเนมสเปซนั้นและทำให้เกิดความสับสน
ล็อกกา

28

5 ปีต่อมาและฉันคิดว่าฉันจะพูดถึงเรื่องนี้ซึ่งทั้งสองก็ดูดีและไม่ได้ชั่วร้าย

using ns1::MyClass;

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

3
นี่คือคำตอบที่ดีที่สุด มันดูสะอาดที่สุดและหลีกเลี่ยงปัญหาเกี่ยวกับเวอร์ชัน 1 ของ OP ซึ่งอาจนำสิ่งต่างๆเข้ามาในเนมสเปซโดยไม่ได้ตั้งใจและ 2 ซึ่งอาจนำสิ่งต่างๆเข้ามาในพื้นที่โลกโดยไม่ได้ตั้งใจ
ayane_m

ใช่นี่เป็นการผสมผสานที่ยอดเยี่ยมของการพิมพ์น้อยกว่า 3 ในขณะที่ยังคงประกาศเจตนาอย่างชัดเจน
jb

14

ฉันใช้เวอร์ชัน 4 (ด้านล่าง) เพราะรวมข้อดีส่วนใหญ่ของเวอร์ชัน 1 (ความสั้นของคำจำกัดความที่แก้ไข) และเวอร์ชัน 3 (ให้ชัดเจนที่สุด) ข้อเสียเปรียบหลักคือผู้คนไม่คุ้นเคยกับมัน แต่เนื่องจากฉันคิดว่าในทางเทคนิคดีกว่าทางเลือกอื่นฉันไม่รังเกียจ

เวอร์ชัน 4: ใช้คุณสมบัติแบบเต็มโดยใช้ชื่อแทนเนมสเปซ:

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

ในโลกของฉันฉันมักใช้นามแฝงเนมสเปซเนื่องจากทุกอย่างมีคุณสมบัติชัดเจน - เว้นแต่จะทำไม่ได้ (เช่นชื่อตัวแปร) หรือเป็นจุดปรับแต่งที่รู้จัก (เช่น swap () ในเทมเพลตฟังก์ชัน)


1
ในขณะที่ฉันคิดว่าตรรกะ "ดีกว่าดังนั้นฉันจึงไม่สนใจว่ามันจะทำให้คนสับสน" หรือไม่ แต่ฉันก็ต้องยอมรับว่านี่เป็นแนวทางที่ดีสำหรับเนมสเปซที่ซ้อนกัน
คุณบอย

1
+1 สำหรับแนวคิด "ทำไมฉันไม่คิดอย่างนั้น" ที่ยอดเยี่ยม! (สำหรับ "ผู้คนไม่คุ้นเคยกับ [สิ่งใหม่ที่เหนือกว่าทางเทคนิค]" พวกเขาจะชินกับมันถ้ามีคนทำมากขึ้น)
wjl

เพื่อให้แน่ใจว่าฉันเข้าใจมีทั้งouterและinnerกำหนดเป็นเนมสเปซในไฟล์ส่วนหัวอื่นแล้วหรือยัง
dekuShrub

4

เวอร์ชัน 3 ทำให้การเชื่อมโยงระหว่างคลาสและเนมสเปซมีความชัดเจนมากโดยเสียค่าใช้จ่ายในการพิมพ์มากขึ้น เวอร์ชัน 1 หลีกเลี่ยงสิ่งนี้ แต่จับการเชื่อมโยงกับบล็อก เวอร์ชัน 2 มีแนวโน้มที่จะซ่อนสิ่งนี้ดังนั้นฉันจึงหลีกเลี่ยงสิ่งนั้น



3

ฉันเลือก Num.3 (หรือที่เรียกว่าเวอร์ชัน verbose) เป็นการพิมพ์มากกว่า แต่เจตนานั้นแน่นอนสำหรับคุณและผู้เรียบเรียง ปัญหาที่คุณโพสต์ไว้นั้นง่ายกว่าโลกแห่งความเป็นจริง ในโลกแห่งความเป็นจริงมีขอบเขตอื่น ๆ สำหรับคำจำกัดความไม่ใช่เฉพาะสมาชิกชั้นเรียน คำจำกัดความของคุณไม่ซับซ้อนมากสำหรับคลาสเท่านั้น - เนื่องจากขอบเขตของพวกเขาจะไม่ถูกเปิดขึ้นมาใหม่ (ไม่เหมือนกับเนมสเปซขอบเขตส่วนกลาง ฯลฯ )

Num.1 สิ่งนี้อาจล้มเหลวด้วยขอบเขตอื่นที่ไม่ใช่คลาส - อะไรก็ตามที่สามารถเปิดใหม่ได้ ดังนั้นคุณอาจประกาศฟังก์ชันใหม่ในเนมสเปซโดยใช้วิธีนี้หรืออินไลน์ของคุณอาจถูกแทนที่ด้วย ODR คุณจะต้องใช้สิ่งนี้สำหรับคำจำกัดความบางอย่าง (โดยเฉพาะอย่างยิ่งความเชี่ยวชาญของเทมเพลต)

Num.2 สิ่งนี้เปราะบางมากโดยเฉพาะในโค้ดเบสขนาดใหญ่เนื่องจากส่วนหัวและการอ้างอิงเปลี่ยนไปโปรแกรมของคุณจะไม่สามารถคอมไพล์ได้

Num.3 นี้เหมาะ แต่มากพิมพ์ - สิ่งที่เจตนาของคุณคือการกำหนดสิ่งที่ สิ่งนี้ทำอย่างนั้นและคอมไพเลอร์เริ่มต้นเพื่อให้แน่ใจว่าคุณไม่ได้ทำผิดคำจำกัดความไม่ได้ซิงค์กับการประกาศ ฯลฯ


3

ปรากฎว่าไม่ใช่แค่ "รูปแบบการเข้ารหัส" เท่านั้น Num. 2 นำไปสู่ข้อผิดพลาดในการเชื่อมโยงเมื่อกำหนดและเริ่มต้นตัวแปรที่ประกาศ extern ในไฟล์ส่วนหัว ดูตัวอย่างในคำถามของฉัน คำจำกัดความของค่าคงที่ภายในเนมสเปซในไฟล์ cpp


2

ทุกวิธีที่ถูกต้องและแต่ละวิธีมีข้อดีและข้อเสีย

ในเวอร์ชัน 1 คุณมีข้อได้เปรียบที่ไม่ต้องเขียนเนมสเปซหน้าแต่ละฟังก์ชัน ข้อเสียคือคุณจะได้รับการระบุตัวตนที่น่าเบื่อโดยเฉพาะอย่างยิ่งหากคุณมีเนมสเปซมากกว่าหนึ่งระดับ

ในเวอร์ชัน 2 คุณทำให้โค้ดของคุณสะอาดขึ้น แต่ถ้าคุณมีมากกว่าหนึ่งเนมสเปซที่ใช้งานใน CPP หนึ่งอาจเข้าถึงฟังก์ชันและตัวแปรของอีกตัวหนึ่งโดยตรงทำให้เนมสเปซของคุณไร้ประโยชน์ (สำหรับไฟล์ cpp นั้น)

ในเวอร์ชัน 3 คุณจะต้องพิมพ์มากขึ้นและสายฟังก์ชันของคุณอาจใหญ่กว่าหน้าจอซึ่งไม่ดีต่อเอฟเฟกต์การออกแบบ

นอกจากนี้ยังมีอีกวิธีหนึ่งที่บางคนใช้ คล้ายกับเวอร์ชันแรก แต่ไม่มีปัญหาในการระบุตัวตน

มันเป็นเช่นนี้:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

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

CLOSE_NS1

ขึ้นอยู่กับคุณว่าจะเลือกอันไหนดีกว่าสำหรับแต่ละสถานการณ์ =]


14
ฉันไม่เห็นเหตุผลใด ๆ ที่จะใช้มาโครที่นี่ หากคุณไม่ต้องการเยื้องก็อย่าเยื้อง การใช้มาโครทำให้โค้ดมีความชัดเจนน้อยลง
tenfour

1
ฉันคิดว่าเวอร์ชันล่าสุดที่คุณพูดถึงมีประโยชน์เมื่อใดก็ตามที่คุณต้องการคอมไพล์โค้ดด้วยคอมไพเลอร์เก่าที่ไม่รองรับเนมสเปซ (ใช่ไดโนเสาร์บางตัวยังอยู่) ในกรณีนี้คุณสามารถวางมาโครไว้ใน#ifdefอนุประโยคได้
Luca Martini

คุณไม่จำเป็นต้องระบุหากคุณไม่ต้องการ แต่ถ้าคุณไม่ได้ใช้มาโคร IDE บางตัวจะพยายามทำเพื่อคุณ ตัวอย่างเช่นใน Visual Studio คุณสามารถเลือกรหัสทั้งหมดแล้วกด ALT + F8 เพื่อระบุอัตโนมัติ หากคุณไม่ใช้คำจำกัดความคุณจะสูญเสียฟังก์ชันการทำงานนั้น นอกจากนี้ฉันไม่คิดว่า OPEN_ (เนมสเปซ) และ CLOSE_ (เนมสเปซ) จะไม่ค่อยชัดเจนถ้าคุณมีในมาตรฐานการเข้ารหัสของคุณ เหตุผลที่ @LucaMartini ให้ก็น่าสนใจเช่นกัน
Renan Greinert

หากสิ่งนี้ถูกสร้างขึ้นโดยทั่วไป#define OPEN_NS(X)ฉันคิดว่ามันมีประโยชน์เล็กน้อยแต่ไม่จริง ... ฉันไม่คัดค้านมาโคร แต่ดูเหมือนว่า OTT เล็กน้อย ฉันคิดว่าแนวทางของ Dietmar Kühlดีกว่าสำหรับเนมสเปซที่ซ้อนกัน
คุณบอย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.