Tại sao "sử dụng không gian tên X;" không được phép trong cấp lớp / cấu trúc?


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

Chỉnh sửa : Muốn biết động lực đằng sau nó.


1
@pst: C # không có bất cứ điều gì giống như using namespace. C # cho phép một cái gì đó tương tự, nhưng chỉ ở phạm vi tệp. C ++ using namespacecho phép bạn kết hợp một không gian tên này vào một không gian tên khác.
Billy ONeal

2
Bản sao của câu hỏi này ?
Greatwolf

@ZachSaw, tôi hiểu mối quan tâm của bạn. Đã cố gắng đóng Qn dựa trên mức độ phù hợp. Vì bài đăng này chứa câu trả lời khách quan hơn và tham chiếu đến tiêu chuẩn, tôi đã giữ nó mở. Trong quá khứ, nhiều Qn cũ của tôi đã bị Qn mới hơn đóng .. đôi khi bởi tôi đôi khi bởi những người khác. Vui lòng gắn cờ cho Mods kim cương, nếu bạn cảm thấy quyết định này không phù hợp. Không cảm thấy khó khăn. :-)
iammilind

@iammilind không thể quan tâm ít hơn đến TBH. Thật là một mớ hỗn độn những ngày này. Nhưng đánh dấu một bài đăng bắt đầu bằng "Tôi không biết chính xác" vì câu trả lời thực sự chứa "câu trả lời khách quan hơn và tham khảo tiêu chuẩn". Haha.
Zach Saw

@ZachSaw, tôi không chỉ nói về câu trả lời được chấp nhận mà còn về bài đăng tổng thể. Có, nó khách quan nhưng trích dẫn tiêu chuẩn có trong câu trả lời này . Nó bắt đầu bằng "Tôi không biết", bởi vì ngay cả trong tiêu chuẩn, nó không được giải thích tại sao "sử dụng không gian tên" không được phép bên trong class/struct. Nó chỉ đơn giản là không được phép. Nhưng câu trả lời được chấp nhận không thảo luận về một lý do rất hợp lý để không cho phép nó. tức là xem xét Hello::Worldở đâu và xem xét ở đâu World. Hy vọng rằng xóa bỏ nghi ngờ.
iammilind

Câu trả lời:


35

Tôi không biết chính xác, nhưng suy đoán của tôi là việc cho phép điều này ở phạm vi lớp có thể gây ra nhầm lẫn:

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?
}

Vì không có cách rõ ràng để làm điều này, tiêu chuẩn chỉ nói rằng bạn không thể.

Bây giờ, lý do điều này ít khó hiểu hơn khi chúng ta đang nói về phạm vi không gian tên:

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, tôi đã nghĩ đến lý do này, nhưng điều tương tự cũng có thể áp dụng cho using namespace Hello;bên trong namespacecũng khác (và khai báo externhàm bên trong nó).
iammilind

10
Tôi không nghĩ nó khó hiểu. C ++ không phải là phỏng đoán. Nếu nó được cho phép, thì ủy ban C ++ ISO sẽ chỉ định trong đặc tả ngôn ngữ. Sau đó, bạn sẽ không nói nó khó hiểu. Nếu không, người ta có thể nói ngay cả điều này là khó hiểu: Ideone.com/npOeD ... nhưng sau đó quy tắc cho mã hóa như vậy được chỉ định trong thông số kỹ thuật.
Nawaz

1
@Nawaz: Hầu hết người dùng ngôn ngữ này. Tôi chưa bao giờ nói C ++ là về phỏng đoán. Tôi đang nói rằng khi thông số kỹ thuật được thiết kế, nó được thiết kế với hành vi mà hầu hết các lập trình viên sẽ mong đợi trước thời hạn. Và các quy tắc trên giấy thường khó hiểu - những nỗ lực tiêu chuẩn để được rõ ràng nhưng nó không phải lúc nào cũng thành công.
Billy ONeal

6
Trong ví dụ đầu tiên, phải là: Hello::World Blah::DoSomething()hoặc Blah::World Blah::DoSomething()(nếu nó được cho phép), kiểu trả về của định nghĩa hàm thành viên không được coi là thuộc phạm vi của lớp trong ngôn ngữ, vì vậy nó phải đủ điều kiện. Hãy xem xét ví dụ hợp lệ về việc thay thế usingbằng typedef Hello::World World;phạm vi lớp. Vì vậy, không nên có bất ngờ ở đó.
David Rodríguez - dribeas

2
Nếu nó được cho phép, tôi tin rằng nó sẽ được áp dụng ở cấp độ từ vựng. Tôi nghĩ đây là giải pháp "hiển nhiên" hầu như không có gì ngạc nhiên.
Thomas Eding

18

Bởi vì tiêu chuẩn C ++ cấm nó một cách rõ ràng. Từ C ++ 03 §7.3.4 [namespace.udir]:

using-chỉ thị :
    sử dụng không gian tên :: opt  lồng nhau-tên-chỉ định opt  không gian tên-tên ;

Một chỉ thị sử dụng sẽ không xuất hiện trong phạm vi lớp, nhưng có thể xuất hiện trong phạm vi không gian tên hoặc trong phạm vi khối. [Lưu ý: khi tra cứu tên không gian tên trong chỉ thị sử dụng, chỉ những tên không gian tên được xem xét, xem 3.4.6. ]

Tại sao tiêu chuẩn C ++ lại cấm nó? Tôi không biết, hãy hỏi một thành viên của ủy ban ISO đã phê duyệt tiêu chuẩn ngôn ngữ.


45
Tuy nhiên, một câu trả lời đúng về mặt kỹ thuật nhưng vô dụng; loại tồi tệ nhất. 1) nhiều người hơn chỉ là ủy ban biết câu trả lời. 2) các thành viên ủy ban tham gia SO 3) nếu bạn không biết câu trả lời (theo tinh thần của câu hỏi) tại sao lại trả lời?
Catskul

5
@Catskul: đó không phải là một câu trả lời vô ích. Sẽ rất hữu ích khi biết rằng tiêu chuẩn giải quyết rõ ràng vấn đề này và cấm nó. Thật trớ trêu khi câu trả lời được ủng hộ nhiều nhất lại bắt đầu bằng "Tôi không biết chính xác". Ngoài ra, "tiêu chuẩn cấm nó" không giống như "nó không được phép vì trình biên dịch không cho phép nó", bởi vì trường hợp sau sẽ không trả lời các câu hỏi tiếp theo như: nó có vấn đề với trình biên dịch của tôi không? là trình biên dịch không tuân thủ tiêu chuẩn? nó là một tác dụng phụ của một số thứ khác mà tôi không biết? vv
antonone

9

Tôi tin rằng lý do chính đáng là nó có thể gây nhầm lẫn. Hiện tại, trong khi xử lý mã định danh cấp lớp, tra cứu trước tiên sẽ tìm kiếm trong phạm vi lớp và sau đó trong không gian tên bao quanh. Việc cho phép using namespaceở cấp độ lớp học sẽ có một số tác dụng phụ về cách thức tra cứu hiện được thực hiện. Đặc biệt, nó sẽ phải được thực hiện đôi khi giữa việc kiểm tra phạm vi lớp cụ thể đó và kiểm tra không gian tên bao quanh. Đó là: 1) hợp nhất cấp lớp và tra cứu cấp không gian tên đã sử dụng, 2) tra cứu không gian tên đã sử dụng sau phạm vi lớp nhưng trước bất kỳ phạm vi lớp nào khác, 3) tra cứu không gian tên đã sử dụng ngay trước không gian tên bao quanh. 4) tra cứu được hợp nhất với không gian tên bao quanh.

  1. Điều này sẽ tạo ra một sự khác biệt lớn, trong đó một số nhận dạng ở cấp lớp sẽ che khuất bất kỳ số nhận dạng nào trong không gian tên bao quanh, nhưng nó sẽ không che bóng một không gian tên đã sử dụng . Hiệu ứng sẽ rất lạ, trong việc truy cập vào không gian tên đã sử dụng từ một lớp trong một không gian tên khác và từ cùng một không gian tên sẽ khác nhau:

.

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. Tra cứu ngay sau phạm vi lớp này. Điều này sẽ có hiệu ứng kỳ lạ là phủ bóng các thành viên của lớp cơ sở. Việc tra cứu hiện tại không kết hợp các tra cứu mức lớp và không gian tên, và khi thực hiện tra cứu lớp, nó sẽ đi đến tất cả các lớp cơ sở trước khi xem xét vùng tên bao quanh. Hành vi sẽ đáng ngạc nhiên ở chỗ nó sẽ không xem xét vùng tên ở mức tương tự với vùng tên bao quanh. Một lần nữa, không gian tên đã sử dụng sẽ được ưu tiên hơn không gian tên bao quanh.

.

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. Tra cứu ngay trước không gian tên bao quanh. Một lần nữa vấn đề với cách tiếp cận này là nó sẽ gây ngạc nhiên cho nhiều người. Hãy xem xét rằng không gian tên được xác định trong một đơn vị dịch khác, do đó không thể nhìn thấy tất cả mã sau cùng một lúc:

.

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. Hợp nhất với không gian tên bao quanh. Điều này sẽ có tác dụng giống hệt như việc áp dụng usingkhai báo ở cấp không gian tên. Nó sẽ không thêm bất kỳ giá trị mới nào vào đó, nhưng mặt khác sẽ làm phức tạp thêm việc tra cứu các trình triển khai trình biên dịch. Tra cứu định danh không gian tên giờ đây độc lập với nơi kích hoạt tìm kiếm trong mã. Khi bên trong một lớp, nếu tra cứu không tìm thấy mã định danh ở phạm vi lớp, nó sẽ quay trở lại tra cứu không gian tên, nhưng đó chính xác là tra cứu không gian tên được sử dụng trong định nghĩa hàm, không cần duy trì trạng thái mới. Khi usingkhai báo được tìm thấy ở cấp không gian tên, nội dung của không gian tên được sử dụng đưa vào không gian tên đó cho tất cả các tra cứu liên quan đến không gian tên. Nếuusing namespace được cho phép ở cấp lớp, sẽ có các kết quả khác nhau đối với việc tra cứu không gian tên của cùng một không gian tên chính xác tùy thuộc vào nơi kích hoạt tra cứu và điều đó sẽ làm cho việc triển khai tra cứu phức tạp hơn nhiều mà không có giá trị bổ sung.

Dù sao, khuyến nghị của tôi là không sử dụng using namespacekhai báo gì cả. Nó làm cho mã đơn giản hơn để lập luận mà không cần phải ghi nhớ tất cả nội dung của không gian tên.


1
Tôi đồng ý rằng việc sử dụng có xu hướng tạo ra những điều kỳ quặc ngầm. Nhưng một số thư viện có thể được thiết kế xung quanh thực tế usingtồn tại. Bằng cách cố ý khai báo mọi thứ trong không gian tên dài lồng nhau. Ví dụ: glmlàm điều đó và sử dụng nhiều thủ thuật để kích hoạt / trình bày các tính năng khi khách hàng sử dụng using.
v.oddou

thậm chí ngay trong STL using namespace std::placeholders. cf en.cppreference.com/w/cpp/utility/filities/bind
v.oddou

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

1

Điều này có lẽ không được phép vì tính mởtính đóng.

  • Các lớp và cấu trúc trong C ++ luôn là các thực thể đóng. Chúng được định nghĩa ở chính xác một nơi (mặc dù bạn có thể tách khai báo và triển khai).
  • không gian tên có thể được mở, mở lại và mở rộng tùy ý thường xuyên.

Việc nhập không gian tên vào các lớp sẽ dẫn đến những trường hợp buồn cười như sau:

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.
}

0

Tôi nghĩ đó là một khiếm khuyết của ngôn ngữ. Bạn có thể sử dụng cách giải quyết bên dưới. Hãy ghi nhớ cách giải quyết này, có thể dễ dàng đề xuất các quy tắc giải quyết xung đột tên cho trường hợp khi ngôn ngữ sẽ được thay đổi.

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
{
}

Bạn có thể vui lòng thêm một số lời giải thích?
Kishan Bharda

Vâng, tôi đã thêm một số ý kiến
naprimeroleg
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.