Không gian tên không tên / ẩn danh so với các hàm tĩnh


508

Một tính năng của C ++ là khả năng tạo các không gian tên không tên (ẩn danh), như vậy:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

Bạn sẽ nghĩ rằng một tính năng như vậy sẽ vô dụng - vì bạn không thể chỉ định tên của không gian tên, nên không thể truy cập bất cứ thứ gì bên trong nó từ bên ngoài. Nhưng các không gian tên không tên này có thể truy cập được trong tệp mà chúng được tạo, như thể bạn có một mệnh đề sử dụng ngầm cho chúng.

Câu hỏi của tôi là, tại sao hoặc khi nào điều này sẽ thích hợp hơn để sử dụng các hàm tĩnh? Hay về cơ bản chúng là hai cách để làm cùng một việc?


13
Trong C ++ 11, việc sử dụng statictrong bối cảnh này không được đánh giá cao ; mặc dù không gian tên không tên là một thay thế vượt trộistatic , nhưng có những trường hợp nó bị lỗi khi staticgiải cứu .
huyền thoại2k

Câu trả lời:


332

Tiêu chuẩn C ++ đọc trong phần 7.3.1.1 Không gian tên không tên, đoạn 2:

Việc sử dụng từ khóa tĩnh không được dùng nữa khi khai báo các đối tượng trong phạm vi không gian tên, không gian tên không tên cung cấp một sự thay thế vượt trội.

Tĩnh chỉ áp dụng cho tên của các đối tượng, hàm và các hiệp hội ẩn danh, không áp dụng cho khai báo kiểu.

Biên tập:

Quyết định từ chối sử dụng từ khóa tĩnh này (ảnh hưởng đến khả năng hiển thị của khai báo biến trong đơn vị dịch) đã bị đảo ngược ( ref ). Trong trường hợp này, sử dụng một không gian tên tĩnh hoặc không tên được quay lại về cơ bản là hai cách để thực hiện cùng một điều chính xác. Để thảo luận thêm xin vui lòng xem câu hỏi SO này .

Các không gian tên không tên vẫn có lợi thế cho phép bạn xác định các kiểu dịch-đơn vị-cục bộ. Xin vui lòng xem câu hỏi SO này để biết thêm chi tiết.

Tín dụng cho Mike Percy đã mang đến sự chú ý của tôi.


39
Head Geek hỏi về từ khóa tĩnh chỉ được sử dụng đối với các chức năng. Từ khóa tĩnh được áp dụng cho thực thể được khai báo trong phạm vi không gian tên chỉ định liên kết bên trong của nó. Thực thể được khai báo trong không gian tên ẩn danh có liên kết bên ngoài (C ++ / 3.5) tuy nhiên nó được đảm bảo sống trong phạm vi được đặt tên duy nhất. Tính ẩn danh này của không gian tên chưa được đặt tên có hiệu quả che giấu khai báo của nó làm cho nó chỉ có thể truy cập được từ bên trong một đơn vị dịch thuật. Cái sau hoạt động hiệu quả theo cách tương tự như từ khóa tĩnh.
mloskot

5
nhược điểm của liên kết bên ngoài là gì? Điều này có thể ảnh hưởng đến nội tuyến?
Alex

17
Những người trong nhóm thiết kế C ++ nói rằng từ khóa tĩnh bị phản đối có lẽ không bao giờ hoạt động với một mã C khổng lồ trong một hệ thống thế giới thực lớn ... (Bạn có thể thấy ngay một từ khóa tĩnh nhưng không phải là không gian tên ẩn danh nếu nó chứa nhiều khai báo với nhận xét lớn khối.)
Calmarius

23
Vì câu trả lời này xuất hiện trên Google như một kết quả hàng đầu cho "không gian tên ẩn danh c ++", nên cần lưu ý rằng việc sử dụng tĩnh không còn bị phản đối nữa. Xem stackoverflow.com/questions/4726570/ Khănopen-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 để biết thêm thông tin.
Michael Percy

2
@ErikAronesty Nghe có vẻ sai. Bạn có một ví dụ tái sản xuất? Kể từ C ++ 11 - và thậm chí trước đó trong một số trình biên dịch - namespaces không được đặt tên ngầm có liên kết nội bộ, do đó không có sự khác biệt. Bất kỳ vấn đề nào trước đây có thể phát sinh từ cách diễn đạt kém đều được giải quyết bằng cách biến điều này thành một yêu cầu trong C ++ 11.
gạch dưới

73

Đặt các phương thức trong một không gian tên ẩn danh sẽ ngăn bạn vô tình vi phạm Quy tắc Một định nghĩa , cho phép bạn không bao giờ lo lắng về việc đặt tên các phương thức trợ giúp của mình giống như một số phương thức khác mà bạn có thể liên kết.

Và, như được chỉ ra bởi luke, các không gian tên ẩn danh được tiêu chuẩn ưa thích hơn các thành viên tĩnh.


2
Tôi đã đề cập đến các hàm độc lập tĩnh (tức là các hàm trong phạm vi tệp), không phải các hàm thành viên tĩnh. Các hàm độc lập tĩnh giống như các hàm trong một không gian tên không tên, do đó, câu hỏi.
Head Geek

2
Ah; tốt, ODR vẫn được áp dụng. Chỉnh sửa để xóa đoạn văn.
hazzen

như tôi nhận được, ODR cho một hàm tĩnh không hoạt động khi được xác định trong tiêu đề và tiêu đề này được bao gồm trong nhiều đơn vị dịch, phải không? trong trường hợp này, bạn nhận được nhiều bản sao của cùng một chức năng
Andriy Tylychko

@Andy T: Bạn không thực sự thấy "nhiều định nghĩa" trong trường hợp tiêu đề được bao gồm. Tiền xử lý chăm sóc nó. Trừ khi có nhu cầu nghiên cứu đầu ra mà bộ tiền xử lý đã tạo ra, với tôi nó trông khá kỳ lạ và hiếm. Ngoài ra, có một cách thực hành tốt để bao gồm "vệ sĩ" trong các tệp tiêu đề, như: "#ifndef SOME_GUARD - #define SOME_GUARD ..." được cho là để ngăn bộ tiền xử lý bao gồm hai tiêu đề giống nhau hai lần.
Nikita Vorontsov

@NikitaVorontsov người bảo vệ có thể ngăn việc đưa cùng một tiêu đề vào cùng một đơn vị dịch, tuy nhiên, nó cho phép nhiều định nghĩa trong các đơn vị dịch khác nhau. Điều này có thể gây ra lỗi liên kết "nhiều định nghĩa" xuống dòng.
Alex

37

Có một trường hợp cạnh mà tĩnh có tác dụng đáng ngạc nhiên (ít nhất là với tôi). Các trạng thái tiêu chuẩn C ++ 03 trong 14.6.4.2/1:

Đối với một lệnh gọi hàm phụ thuộc vào tham số mẫu, nếu tên hàm là id không đủ tiêu chuẩn nhưng không phải id mẫu , các hàm ứng cử viên được tìm thấy bằng cách sử dụng quy tắc tra cứu thông thường (3.4.1, 3.4.2) ngoại trừ:

  • Đối với phần tra cứu bằng cách sử dụng tra cứu tên không đủ tiêu chuẩn (3.4.1), chỉ có các khai báo hàm với liên kết ngoài từ ngữ cảnh định nghĩa mẫu được tìm thấy.
  • Đối với phần tra cứu sử dụng các không gian tên được liên kết (3.4.2), chỉ có các khai báo hàm với liên kết ngoài được tìm thấy trong bối cảnh định nghĩa mẫu hoặc bối cảnh khởi tạo mẫu được tìm thấy.

...

Mã dưới đây sẽ gọi foo(void*)và không foo(S const &)như bạn mong đợi.

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

Bản thân nó có lẽ không phải là vấn đề lớn, nhưng nó nhấn mạnh rằng đối với trình biên dịch C ++ tuân thủ đầy đủ (nghĩa là có hỗ trợ export), statictừ khóa sẽ vẫn có chức năng không có sẵn theo bất kỳ cách nào khác.

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

Cách duy nhất để đảm bảo rằng hàm trong không gian tên chưa được đặt tên của chúng tôi sẽ không được tìm thấy trong các mẫu sử dụng ADL là tạo ra nó static.

Cập nhật cho C ++ hiện đại

Kể từ C ++ '11, các thành viên của một không gian tên chưa được đặt tên có liên kết nội bộ ngầm (3.5 / 4):

Một không gian tên không tên hoặc một không gian tên được khai báo trực tiếp hoặc gián tiếp trong một không gian tên không tên có liên kết bên trong.

Nhưng đồng thời, 14.6.4.2/1 đã được cập nhật để xóa đề cập đến liên kết (điều này được lấy từ C ++ '14):

Đối với một lệnh gọi hàm trong đó biểu thức postfix là tên phụ thuộc, các hàm ứng cử viên được tìm thấy bằng cách sử dụng các quy tắc tra cứu thông thường (3.4.1, 3.4.2) ngoại trừ:

  • Đối với phần tra cứu bằng cách sử dụng tra cứu tên không đủ tiêu chuẩn (3.4.1), chỉ có các khai báo hàm từ ngữ cảnh định nghĩa mẫu được tìm thấy.

  • Đối với phần tra cứu sử dụng các không gian tên được liên kết (3.4.2), chỉ có các khai báo hàm được tìm thấy trong bối cảnh định nghĩa mẫu hoặc bối cảnh khởi tạo mẫu được tìm thấy.

Kết quả là sự khác biệt đặc biệt này giữa các thành viên không gian tên tĩnh và không tên không còn tồn tại.


3
Không phải là từ khóa xuất khẩu được cho là chết lạnh? Các trình biên dịch duy nhất hỗ trợ "xuất khẩu" là các trình thử nghiệm và trừ khi có bất ngờ, "xuất khẩu" thậm chí sẽ không được triển khai ở các trình biên dịch khác vì các tác dụng phụ không mong muốn (ngoài việc không được thực hiện như mong đợi)
paercebal

2
Xem bài viết của Herb Sutter trên subjet: gotw.ca/publications/mill23-x.htm
paercebal

3
Mặt trước từ Edison Design Group (EDG) là bất cứ điều gì ngoài thử nghiệm. Nó gần như chắc chắn là việc thực hiện C ++ phù hợp tiêu chuẩn nhất trên thế giới. Trình biên dịch Intel C ++ sử dụng EDG.
Richard Corden

1
Tính năng C ++ nào không có 'tác dụng phụ không mong muốn'? Trong trường hợp xuất, có nghĩa là một hàm không gian tên chưa được đặt tên sẽ được tìm thấy từ một TU khác - giống như khi bạn đưa trực tiếp định nghĩa mẫu. Sẽ ngạc nhiên hơn nếu nó không như thế này!
Richard Corden

Tôi nghĩ rằng bạn có một lỗi đánh máy ở đó - NS::Sđể làm việc, không Scần phải ở bên trong namespace {}?
Eric

12

Gần đây tôi đã bắt đầu thay thế các từ khóa tĩnh bằng các không gian tên ẩn danh trong mã của mình nhưng ngay lập tức gặp phải một vấn đề trong đó các biến trong không gian tên không còn có sẵn để kiểm tra trong trình gỡ lỗi của tôi. Tôi đã sử dụng VC60, vì vậy tôi không biết đó có phải là vấn đề với các trình gỡ lỗi khác không. Cách giải quyết của tôi là xác định không gian tên 'mô-đun', nơi tôi đặt cho nó tên của tệp cpp của mình.

Ví dụ: trong tệp XmlUtil.cpp của tôi, tôi xác định một không gian tên XmlUtil_I { ... }cho tất cả các biến và hàm mô-đun của mình. Bằng cách đó tôi có thể áp dụng trình XmlUtil_I::độ trong trình gỡ lỗi để truy cập các biến. Trong trường hợp này, _Iphân biệt nó với một không gian tên công khai như XmlUtiltôi có thể muốn sử dụng ở nơi khác.

Tôi cho rằng một nhược điểm tiềm tàng của phương pháp này so với phương pháp ẩn danh thực sự là ai đó có thể vi phạm phạm vi tĩnh mong muốn bằng cách sử dụng vòng loại không gian tên trong các mô-đun khác. Tôi không biết nếu đó là một mối quan tâm chính mặc dù.


7
Tôi cũng đã làm điều này, nhưng với #if DEBUG namespace BlahBlah_private { #else namespace { #endif, vì vậy "không gian tên mô-đun" chỉ hiện diện trong các bản dựng gỡ lỗi và không gian tên ẩn danh thực sự được sử dụng theo cách khác. Sẽ thật tuyệt nếu các trình sửa lỗi đưa ra một cách hay để xử lý việc này. Doxygen cũng bị nhầm lẫn bởi nó.
Kristopher Johnson

4
không gian tên không tên không thực sự là một sự thay thế khả thi cho tĩnh. tĩnh có nghĩa là "thực sự điều này không bao giờ được liên kết với TU". không gian tên không tên có nghĩa là "nó vẫn được xuất, dưới dạng tên ngẫu nhiên, trong trường hợp nó được gọi từ một lớp cha mẹ nằm ngoài TU" ...
Erik Aronesty

7

Việc sử dụng từ khóa tĩnh cho mục đích đó không được chấp nhận theo tiêu chuẩn C ++ 98. Vấn đề với tĩnh là nó không áp dụng cho định nghĩa kiểu. Đây cũng là một từ khóa quá tải được sử dụng theo nhiều cách khác nhau trong các ngữ cảnh khác nhau, vì vậy các không gian tên không tên được đơn giản hóa mọi thứ một chút.


1
Nếu bạn muốn sử dụng một loại chỉ trong một đơn vị dịch thuật thì hãy khai báo nó bên trong tệp .cpp. Nó sẽ không thể truy cập từ các đơn vị dịch thuật khác.
Calmarius

4
Bạn có nghĩ rằng, phải không? Nhưng nếu một đơn vị dịch thuật khác (= cpp-file) trong cùng một ứng dụng từng tuyên bố một loại có cùng tên, thì bạn đang gặp vấn đề khá khó gỡ lỗi :-). Ví dụ, bạn có thể kết thúc với các tình huống trong đó vtable cho một trong các loại được sử dụng khi gọi các phương thức khác.
avl_sweden

1
Không được tán thành nữa. Và loại defs không được xuất, vì vậy điều đó là vô nghĩa. statics là hữu ích cho các chức năng độc lập và bình toàn cầu. không gian tên không tên là hữu ích cho các lớp.
Erik Aronesty

6

Từ kinh nghiệm tôi sẽ chỉ lưu ý rằng trong khi đó là cách C ++ để đặt các hàm trước đây tĩnh vào không gian tên ẩn danh, trình biên dịch cũ hơn đôi khi có thể gặp vấn đề với điều này. Tôi hiện đang làm việc với một vài trình biên dịch cho các nền tảng đích của chúng tôi và trình biên dịch Linux hiện đại hơn cũng ổn với việc đặt các hàm vào không gian tên ẩn danh.

Nhưng một trình biên dịch cũ hơn chạy trên Solaris, mà chúng tôi mong muốn cho đến khi một bản phát hành trong tương lai không xác định, đôi khi sẽ chấp nhận nó, và lần khác đánh dấu nó là một lỗi. Lỗi không phải là điều khiến tôi lo lắng, đó là những gì nó thể làm khi chấp nhận nó. Vì vậy, cho đến khi chúng ta hiện đại trên bảng, chúng ta vẫn đang sử dụng các hàm tĩnh (thường là trong phạm vi lớp), nơi chúng ta muốn không gian tên ẩn danh.


3

Ngoài ra, nếu người ta sử dụng từ khóa tĩnh trên một biến như ví dụ này:

namespace {
   static int flag;
}

Nó sẽ không được nhìn thấy trong tập tin ánh xạ


7
Sau đó, bạn không cần không gian tên nặc danh.
Calmarius

2

Một sự khác biệt cụ thể của trình biên dịch giữa các không gian tên ẩn danh và các hàm tĩnh có thể được nhìn thấy khi biên dịch mã sau đây.

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

Biên dịch mã này với VS 2017 (chỉ định cờ cảnh báo cấp 4 / W4 để bật cảnh báo C4505: chức năng cục bộ không được kiểm soát đã bị xóa ) và gcc 4.9 với chức năng -Wunuse hoặc -Wall cho thấy VS 2017 sẽ chỉ tạo cảnh báo cho hàm tĩnh không sử dụng. gcc 4.9 trở lên, cũng như clang 3.3 trở lên, sẽ tạo ra các cảnh báo cho hàm không được ước tính trong không gian tên và cũng là một cảnh báo cho hàm tĩnh không sử dụng.

Bản demo trực tiếp của gcc 4.9 và MSVC 2017


2

Cá nhân tôi thích các hàm tĩnh hơn các không gian tên không tên vì các lý do sau:

  • Rõ ràng và rõ ràng từ định nghĩa hàm một mình rằng nó là riêng tư cho đơn vị dịch thuật được biên dịch. Với không gian tên không tên, bạn có thể cần phải cuộn và tìm kiếm để xem hàm có nằm trong không gian tên không.

  • Các hàm trong không gian tên có thể được xử lý như bên ngoài bởi một số trình biên dịch (cũ hơn). Trong VS2017 họ vẫn ở bên ngoài. Vì lý do này ngay cả khi một hàm nằm trong không gian tên không tên, bạn vẫn có thể muốn đánh dấu chúng tĩnh.

  • Các hàm tĩnh hoạt động rất giống nhau trong C hoặc C ++, trong khi các không gian tên không rõ ràng chỉ là C ++. không gian tên không tên cũng thêm cấp độ thụt lề và tôi không thích điều đó :)

Vì vậy, tôi rất vui khi thấy rằng việc sử dụng tĩnh cho các chức năng không còn bị phản đối nữa .


Các chức năng trong không gian tên ẩn danh được cho là có liên kết bên ngoài. Họ chỉ đọc sai để làm cho chúng độc đáo. Chỉ statictừ khóa thực sự áp dụng liên kết cục bộ cho một chức năng. Ngoài ra, chắc chắn chỉ có một kẻ mất trí điên cuồng thực sự sẽ thêm thụt vào không gian tên?
Roflawaii4

0

Vừa mới biết đến tính năng này khi đọc câu hỏi của bạn, tôi chỉ có thể suy đoán. Điều này dường như cung cấp một số lợi thế so với biến tĩnh ở cấp độ tệp:

  • Các không gian tên ẩn danh có thể được lồng vào nhau, cung cấp nhiều cấp độ bảo vệ mà từ đó các biểu tượng không thể thoát ra.
  • Một số không gian tên ẩn danh có thể được đặt trong cùng một tệp nguồn, tạo ra các phạm vi mức tĩnh khác nhau trong cùng một tệp.

Tôi muốn tìm hiểu nếu có ai đã sử dụng không gian tên ẩn danh trong mã thực.


4
Suy đoán tốt, nhưng sai. Phạm vi của các không gian tên này là toàn tệp.
Konrad Rudolph

Không chính xác, nếu bạn xác định một không gian tên ẩn danh bên trong một không gian tên khác thì nó vẫn chỉ rộng bằng tệp và chỉ có thể được xem là nằm trong không gian tên đó. Thử nó.
Greg Rogers

Tôi có thể sai nhưng, tôi đoán rằng không, nó không phải là tệp rộng: Nó chỉ có thể truy cập được mã sau không gian tên ẩn danh. Đây là một điều tinh tế và thông thường, tôi sẽ không muốn làm ô nhiễm một nguồn có nhiều không gian tên ẩn danh ... Tuy nhiên, điều này có thể có cách sử dụng.
paercebal

0

Sự khác biệt là tên của mã định danh được đọc sai ( _ZN12_GLOBAL__N_11bEvs _ZL1b, điều này không thực sự quan trọng, nhưng cả hai đều được lắp ráp thành các ký hiệu cục bộ trong bảng ký hiệu (không có lệnh .globalasm).

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

Đối với một không gian tên ẩn danh lồng nhau:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

Tất cả các không gian tên ẩn danh cấp 1 trong đơn vị dịch được kết hợp với nhau, Tất cả các không gian tên ẩn danh cấp 2 lồng nhau trong đơn vị dịch được kết hợp với nhau

Bạn cũng có thể có một không gian tên lồng nhau (nội tuyến) trong một không gian tên ẩn danh

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

Bạn cũng có thể có các không gian tên nội tuyến ẩn danh, nhưng theo như tôi có thể nói, inlinetrên một không gian tên ẩn danh có 0 hiệu ứng

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Zcó nghĩa đây là một định danh sai lệch. Lcó nghĩa là nó là một biểu tượng địa phương thông qua static. 1là độ dài của mã định danh bvà sau đó là mã định danhb

_ZN12_GLOBAL__N_11aE _Zcó nghĩa đây là một định danh sai lệch. Ncó nghĩa đây là không gian tên 12là độ dài của tên không gian tên ẩn danh _GLOBAL__N_1, sau đó là tên không gian tên ẩn danh _GLOBAL__N_1, sau đó 1là độ dài của mã định danh a, alà mã định danh aEđóng định danh nằm trong không gian tên.

_ZN12_GLOBAL__N_11A1aE giống như trên, ngoại trừ có một mức không gian tên khác trong đó 1A

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.