ทำไม“ ใช้เนมสเปซ X;” ไม่อนุญาตให้อยู่ในระดับชั้น / โครงสร้าง?


90
class C {
  using namespace std;  // error
};
namespace N {
  using namespace std; // ok
}
int main () {
  using namespace std; // ok
}

แก้ไข : ต้องการทราบแรงจูงใจเบื้องหลัง


1
@pst: C # ไม่มีอะไรเหมือนusing namespace. C # อนุญาตสิ่งที่คล้ายกัน แต่ในขอบเขตไฟล์เท่านั้น C ++ using namespaceช่วยให้คุณสามารถรวมเนมสเปซหนึ่งเข้ากับอีกเนมสเปซได้
Billy ONeal

2
คำถามนี้ซ้ำกันไหม
greatwolf

@ ZachSaw ฉันเข้าใจความห่วงใยของคุณ ได้พยายามปิด Qn ตามความเกี่ยวข้อง เนื่องจากโพสต์นี้มีคำตอบที่เป็นวัตถุประสงค์และอ้างอิงถึงมาตรฐานมากขึ้นฉันจึงเปิดไว้ ในอดีต Qn รุ่นเก่าของฉันหลายคนถูกปิดโดย Qn ที่ใหม่กว่า .. บางครั้งฉันก็โดนคนอื่น โปรดตั้งค่าสถานะเป็นเพชร Mods หากคุณรู้สึกว่าการตัดสินใจนี้ไม่เหมาะสม ไม่มีความรู้สึกยาก. :-)
iammilind

@iammilind ไม่สามารถดูแล TBH น้อยลง วันนี้มันยุ่งมาก แต่การทำเครื่องหมายโพสต์ที่ขึ้นต้นด้วย "ฉันไม่ทราบแน่ชัด" เนื่องจากคำตอบมี "คำตอบที่ตรงกว่าและอ้างอิงถึงมาตรฐาน" ฮ่า ๆ .
Zach Saw

@ ZachSaw ฉันไม่ได้แค่พูดถึงคำตอบที่ได้รับการยอมรับ แต่เป็นการโพสต์โดยรวม ใช่มันมีวัตถุประสงค์ แต่คำพูดมาตรฐานมีอยู่ในคำตอบนี้ มันเริ่มต้นด้วย "ผมไม่ทราบ" เพราะแม้จะอยู่ในมาตรฐานก็จะไม่เป็นธรรมทำไม "ใช้ namespace" class/structไม่ได้รับอนุญาตภายใน มันไม่ได้รับอนุญาต แต่คำตอบที่ได้รับการยอมรับนั้นกล่าวถึงเหตุผลที่สมเหตุสมผลที่จะไม่อนุญาต เช่นการที่จะพิจารณาและสถานที่ที่จะต้องพิจารณาHello::World Worldหวังว่าจะคลายข้อสงสัย
iammilind

คำตอบ:


36

ฉันไม่รู้แน่ชัด แต่ฉันเดาว่าการปล่อยให้สิ่งนี้ในขอบเขตชั้นเรียนอาจทำให้เกิดความสับสน:

namespace Hello
{
    typedef int World;
}

class Blah
{
    using namespace Hello;
public:
    World DoSomething();
}

//Should this be just World or Hello::World ?
World Blah::DoSomething()
{
    //Is the using namespace valid in here?
}

เนื่องจากไม่มีวิธีที่ชัดเจนในการทำเช่นนี้มาตรฐานจึงบอกว่าคุณทำไม่ได้

ตอนนี้เหตุผลที่ทำให้สับสนน้อยลงเมื่อเราพูดถึงขอบเขตเนมสเปซ:

namespace Hello
{
    typedef int World;
}

namespace Other
{
    using namespace Hello;
    World DoSomething();
}

//We are outside of any namespace, so we have to fully qualify everything. Therefore either of these are correct:

//Hello was imported into Other, so everything that was in Hello is also in Other. Therefore this is okay:
Other::World Other::DoSomething()
{
    //We're outside of a namespace; obviously the using namespace doesn't apply here.
    //EDIT: Apparently I was wrong about that... see comments. 
}

//The original type was Hello::World, so this is okay too.
Hello::World Other::DoSomething()
{
    //Ditto
}

namespace Other
{
    //namespace Hello has been imported into Other, and we are inside Other, so therefore we never need to qualify anything from Hello.
    //Therefore this is unambiguiously right
    World DoSomething()
    {
        //We're inside the namespace, obviously the using namespace does apply here.
    }
}

5
+1 ฉันคิดถึงเหตุผลนี้ แต่สิ่งเดียวกันก็ใช้ได้กับusing namespace Hello;ภายในอื่น ๆnamespaceเช่นกัน (และการประกาศexternฟังก์ชันข้างใน)
iammilind

10
ฉันไม่คิดว่ามันสับสน C ++ ไม่เกี่ยวกับการคาดเดา หากได้รับอนุญาตคณะกรรมการ C ++ ISO จะระบุไว้ในข้อกำหนดภาษา แล้วคุณจะไม่พูดว่ามันสับสน มิฉะนั้นอาจมีคนพูดว่าแม้จะสับสน: ideone.com/npOeD ... แต่กฎสำหรับการเข้ารหัสดังกล่าวระบุไว้ในข้อมูลจำเพาะ
Nawaz

1
@Nawaz: ผู้ใช้ภาษาส่วนใหญ่ ฉันไม่เคยพูดว่า C ++ เกี่ยวกับการคาดเดา ฉันกำลังบอกว่าเมื่อได้รับการออกแบบสเป็คแล้วมันถูกออกแบบมาโดยคำนึงถึงพฤติกรรมที่โปรแกรมเมอร์ส่วนใหญ่คาดหวังไว้ล่วงหน้า และกฎบนกระดาษมักจะทำให้สับสน - มาตรฐานพยายามที่จะไม่คลุมเครือ แต่ก็ไม่ประสบความสำเร็จเสมอไป
Billy ONeal

6
ในตัวอย่างแรกควรเป็น: Hello::World Blah::DoSomething()หรือBlah::World Blah::DoSomething()(ถ้าได้รับอนุญาต) ประเภทการส่งคืนของนิยามฟังก์ชันสมาชิกจะไม่ถือว่าอยู่ในขอบเขตของคลาสในภาษาดังนั้นจึงต้องมีคุณสมบัติ พิจารณาตัวอย่างที่ถูกต้องของการแทนที่usingด้วยtypedef Hello::World World;ขอบเขตคลาส ดังนั้นจึงไม่ควรมีเรื่องน่าประหลาดใจที่นั่น
David Rodríguez - dribeas

2
หากได้รับอนุญาตฉันเชื่อว่าจะนำไปใช้ในระดับขอบเขตคำศัพท์ ฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่ "ชัดเจน" และแทบจะไม่มีอะไรน่าพอใจเลย
Thomas Eding

19

เนื่องจากมาตรฐาน C ++ ห้ามไว้อย่างชัดเจน จาก C ++ 03 §7.3.4 [namespace.udir]:

การใช้คำสั่ง :
    โดยใช้ namespace :: การเลือก ที่ซ้อนกันชื่อ-ระบุการเลือก namespace ชื่อ ;

ใช้-สั่งจะไม่ปรากฏในขอบเขตของคลาส แต่อาจปรากฏอยู่ในขอบเขต namespace หรืออยู่ในขอบเขตบล็อก [หมายเหตุ: เมื่อค้นหาชื่อเนมสเปซในคำสั่งการใช้จะพิจารณาเฉพาะชื่อเนมสเปซเท่านั้นดู 3.4.6 ]

เหตุใดมาตรฐาน C ++ จึงห้ามไว้ ฉันไม่ทราบให้ถามสมาชิกของคณะกรรมการ ISO ที่อนุมัติมาตรฐานภาษา


48
อีกคำตอบที่ถูกต้องทางเทคนิค แต่ไร้ประโยชน์ ชนิดที่แย่ที่สุด 1) มีคนมากกว่าแค่คณะกรรมการเท่านั้นที่รู้คำตอบ 2) กรรมการมีส่วนร่วมใน SO 3) หากคุณไม่ทราบคำตอบ (จากจิตวิญญาณของคำถาม) ทำไมต้องตอบเลย?
Catskul

7
@Catskul: ไม่ใช่คำตอบที่ไร้ประโยชน์ เป็นประโยชน์มากที่จะทราบว่ามาตรฐานระบุถึงสิ่งนี้อย่างชัดเจนและห้ามไว้ นอกจากนี้ยังน่าขันที่คำตอบที่ได้รับการโหวตมากที่สุดเริ่มต้นด้วย "ฉันไม่รู้แน่ชัด" นอกจากนี้ "มาตรฐานห้ามใช้" ไม่เหมือนกับ "ไม่อนุญาตเนื่องจากคอมไพเลอร์ไม่อนุญาต" เนื่องจากกรณีหลังจะไม่ตอบคำถามติดตามผลเช่น: มันเป็นปัญหากับคอมไพเลอร์ของฉันหรือไม่? คอมไพเลอร์ไม่เป็นไปตามมาตรฐานหรือไม่ เป็นผลข้างเคียงของสิ่งอื่นที่ฉันไม่ทราบหรือไม่? ฯลฯ
antonone

9

ฉันเชื่อว่าเหตุผลคือมันอาจจะสับสน ขณะนี้ในขณะประมวลผลตัวระบุระดับคลาสการค้นหาจะค้นหาในขอบเขตคลาสก่อนจากนั้นในเนมสเปซที่แนบมา การอนุญาตusing namespaceในระดับชั้นเรียนจะมีผลข้างเคียงค่อนข้างมากกับการดำเนินการค้นหาในขณะนี้ โดยเฉพาะอย่างยิ่งจะต้องดำเนินการบางครั้งระหว่างการตรวจสอบขอบเขตคลาสนั้น ๆ และการตรวจสอบเนมสเปซที่แนบมา นั่นคือ: 1) รวมระดับคลาสและใช้การค้นหาระดับเนมสเปซ 2) ค้นหาเนมสเปซที่ใช้หลังขอบเขตคลาส แต่ก่อนขอบเขตคลาสอื่น ๆ 3) ค้นหาเนมสเปซที่ใช้ก่อนปิดเนมสเปซ 4) การค้นหาที่ผสานกับเนมสเปซที่แนบมา

  1. นี้จะทำให้ความแตกต่างใหญ่ที่ตัวระบุในระดับชั้นจะเงาระบุใด ๆ ใน namespace ล้อมรอบ แต่มันจะไม่เงาใช้ namespace เอฟเฟกต์จะแปลกในการเข้าถึงเนมสเปซที่ใช้แล้วจากคลาสในเนมสเปซที่แตกต่างกันและจากเนมสเปซเดียวกันจะแตกต่างกัน:

.

namespace A {
   void foo() {}
   struct B {
      struct foo {};
      void f() {
         foo();      // value initialize a A::B::foo object (current behavior)
      }
   };
}
struct C {
   using namespace A;
   struct foo {};
   void f() {
      foo();         // call A::foo
   }
};
  1. ค้นหาทันทีหลังจากขอบเขตของคลาสนี้ สิ่งนี้จะมีเอฟเฟกต์แปลก ๆ ของสมาชิกคลาสพื้นฐานที่เป็นเงา การค้นหาในปัจจุบันไม่ได้ผสมการค้นหาระดับคลาสและเนมสเปซและเมื่อทำการค้นหาคลาสมันจะไปจนถึงคลาสพื้นฐานก่อนที่จะพิจารณาเนมสเปซที่แนบมา พฤติกรรมจะน่าแปลกใจที่จะไม่พิจารณาเนมสเปซในระดับที่ใกล้เคียงกับเนมสเปซที่ปิดล้อม อีกครั้งเนมสเปซที่ใช้จะถูกจัดลำดับความสำคัญเหนือเนมสเปซที่ปิดล้อม

.

namespace A {
   void foo() {}
}
void bar() {}
struct base {
   void foo();
   void bar();
};
struct test : base {
   using namespace A;
   void f() {
      foo();           // A::foo()
      bar();           // base::bar()
   }
};
  1. ค้นหาก่อนเนมสเปซที่ปิดล้อม ปัญหาเกี่ยวกับแนวทางนี้เป็นอีกครั้งที่หลายคนน่าจะแปลกใจ พิจารณาว่าเนมสเปซถูกกำหนดไว้ในหน่วยการแปลอื่นเพื่อไม่ให้เห็นโค้ดต่อไปนี้ทั้งหมดพร้อมกัน:

.

namespace A {
   void foo( int ) { std::cout << "int"; }
}
void foo( double ) { std::cout << "double"; }
struct test {
   using namespace A;
   void f() {
      foo( 5.0 );          // would print "int" if A is checked *before* the
                           // enclosing namespace
   }
};
  1. ผสานกับเนมสเปซที่ปิดล้อม สิ่งนี้จะมีผลเหมือนกับการใช้การusingประกาศในระดับเนมสเปซ มันจะไม่เพิ่มค่าใหม่ใด ๆ ให้กับสิ่งนั้น แต่ในทางกลับกันการค้นหาตัวดำเนินการคอมไพเลอร์จะซับซ้อน ขณะนี้การค้นหาตัวระบุเนมสเปซไม่ขึ้นอยู่กับตำแหน่งที่ทริกเกอร์การค้นหาในโค้ด เมื่ออยู่ในคลาสหากการค้นหาไม่พบตัวระบุที่ขอบเขตคลาสมันจะกลับไปที่การค้นหาเนมสเปซ แต่นั่นคือการค้นหาเนมสเปซเดียวกันกับที่ใช้ในนิยามฟังก์ชันไม่จำเป็นต้องรักษาสถานะใหม่ เมื่อusingพบการประกาศที่ระดับเนมสเปซเนื้อหาของเนมสเปซที่ใช้จะถูกนำเข้าสู่เนมสเปซนั้นสำหรับการค้นหาทั้งหมดที่เกี่ยวข้องกับเนมสเปซ ถ้าusing namespace ได้รับอนุญาตในระดับชั้นเรียนจะมีผลลัพธ์ที่แตกต่างกันสำหรับการค้นหาเนมสเปซของเนมสเปซเดียวกันโดยขึ้นอยู่กับว่าการค้นหาถูกเรียกใช้จากที่ใดและจะทำให้การใช้งานการค้นหามีความซับซ้อนมากขึ้นโดยไม่มีค่าเพิ่มเติม

อย่างไรก็ตามคำแนะนำของฉันคือไม่ใช้การusing namespaceประกาศเลย ทำให้โค้ดใช้เหตุผลได้ง่ายขึ้นโดยไม่ต้องคำนึงถึงเนื้อหาของเนมสเปซทั้งหมด


1
ฉันยอมรับว่าการใช้มีแนวโน้มที่จะสร้างความแปลกประหลาดโดยปริยาย แต่ห้องสมุดบางแห่งอาจได้รับการออกแบบตามความเป็นจริงที่usingมีอยู่ โดยเจตนาประกาศสิ่งต่างๆในเนมสเปซแบบยาวที่ซ้อนกันลึก ๆ เช่นglmไม่ว่าและใช้เทคนิคหลายเพื่อเปิดใช้งานคุณสมบัติ / usingปัจจุบันเมื่อการใช้งานของลูกค้า
v.oddou

แม้กระทั่งสิทธิในการ using namespace std::placeholdersSTL cf th.cppreference.com/w/cpp/utility/functional/bind
v.oddou

@ v.oddou:namespace ph = std::placeholders;
David Rodríguez - dribeas

1

สิ่งนี้อาจไม่ได้รับอนุญาตเนื่องจากการเปิดกว้างและการปิด

  • คลาสและโครงสร้างใน C ++ เป็นเอนทิตีปิดเสมอ มีการกำหนดไว้ในที่เดียว (แม้ว่าคุณสามารถแยกการประกาศและการนำไปใช้งานได้)
  • เนมสเปซสามารถเปิดเปิดใหม่และขยายได้โดยพลการบ่อยๆ

การนำเข้าเนมสเปซเข้าสู่ชั้นเรียนอาจทำให้เกิดกรณีตลก ๆ เช่นนี้:

namespace Foo {}

struct Bar { using namespace Foo; };

namespace Foo {
using Baz = int; // I've just extended `Bar` with a type alias!
void baz(); // I've just extended `Bar` with what looks like a static function!
// etc.
}

หรือเราไม่สามารถกำหนดสมาชิกชั้นเรียนด้วยชื่อที่นำเข้าได้ ให้โครงสร้างนี้เพิ่มnamespace Fooลำดับการค้นหาสำหรับโค้ดทั้งหมดภายในนิยามประเภทของstruct Barเช่นเดียวกับการใส่บรรทัดนั้นในทุกฟังก์ชันของสมาชิกแบบอินไลน์ยกเว้นว่าจะใช้งานสำหรับตัวเริ่มต้นแบบรั้งหรือเท่ากับเป็นต้น แต่ก็ยังคง หมดอายุที่วงเล็บปีกกาปิดเช่นเดียวกับusing namespaceภายในร่างกายฟังก์ชันของสมาชิก ตอนนี้ดูเหมือนจะไม่มีวิธีใดเลยในการใช้ Koenig-with-fallback lookup ในตัวเริ่มต้นแบบรั้งหรือเท่ากันโดยไม่ทำให้เนมสเปซที่ปิดล้อมอยู่
Ben Voigt

0

ฉันคิดว่ามันเป็นความบกพร่องของภาษา คุณอาจใช้วิธีแก้ปัญหาด้านล่าง โปรดทราบว่าวิธีแก้ปัญหานี้เป็นเรื่องง่ายที่จะแนะนำกฎของการแก้ปัญหาความขัดแย้งของชื่อสำหรับกรณีที่จะเปลี่ยนภาษา

namespace Hello
{
    typedef int World;
}
// surround the class (where we want to use namespace Hello)
// by auxiliary namespace (but don't use anonymous namespaces in h-files)
namespace Blah_namesp {
using namespace Hello;

class Blah
{
public:
    World DoSomething1();
    World DoSomething2();
    World DoSomething3();
};

World Blah::DoSomething1()
{
}

} // namespace Blah_namesp

// "extract" class from auxiliary namespace
using Blah_namesp::Blah;

Hello::World Blah::DoSomething2()
{
}
auto Blah::DoSomething3() -> World
{
}

คุณช่วยเพิ่มคำอธิบายได้ไหม
Kishan Bharda

ใช่ฉันได้เพิ่มความคิดเห็น
naprimeroleg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.