7

Почему стоит предпочитать неназначенные пространства имен функциям с static?

1

У C++ есть возможность создавать анонимные (безымянные) пространства имен, вот так:

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

На первый взгляд может показаться, что такая функция бесполезна — поскольку вы не можете задать имя пространства имен, невозможно получить доступ к его содержимому извне. Тем не менее, эти безымянные пространства имен доступны в пределах файла, где они созданы, как если бы у вас была неявная директива using.

У меня вопрос: в каких случаях использование анонимных пространств имен предпочтительнее по сравнению с использованием статических функций? Или это по сути два способа достижения одной и той же цели?

5 ответ(ов)

0

Есть один крайний случай, где использование ключевого слова static приводит к неожиданным последствиям (по крайней мере, это было неожиданно для меня). Стандарт C++03 в разделе 14.6.4.2/1 гласит:

Для вызова функции, зависящего от параметра шаблона, если имя функции является unqualified-id, но не template-id, кандидатные функции ищутся с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:

  • Для части поиска, использующей unqualified name lookup (3.4.1), находятся только объявления функций с внешней связью из контекста определения шаблона.
  • Для части поиска, использующей связные пространства имен (3.4.2), находятся только объявления функций с внешней связью, найденные либо в контексте определения шаблона, либо в контексте инстанциации шаблона.

...

Ниже приведенный код вызовет foo(void*), а не foo(S const &), как можно было бы ожидать:

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 &);   // Не учитывается 14.6.4.2(b1)
  }
}

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

Сам по себе этот момент, вероятно, не так уж важен, но он подчеркивает, что для полностью соответствующего стандарту C++ компилятора (то есть поддерживающего export) ключевое слово static все равно будет иметь функционал, который недоступен другим способом.

// 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);  // Будет найден другим TU 'bar.cc'
  }
}

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

Единственный способ гарантировать, что функция из нашего анонимного пространства имен не будет найдена в шаблонах, использующих ADL, — это сделать ее static.

Обновление для современного C++

Начиная с C++11, члены анонимного пространства имен имеют внутреннюю связь по умолчанию (3.5/4):

Неименованное пространство имен или пространство имен, объявленное непосредственно или опосредованно внутри неименованного пространства имен, имеет внутреннюю связь.

Однако, в то же время, 14.6.4.2/1 было обновлено так, чтобы убрать упоминание о связи (это взято из C++14):

Для вызова функции, где постфикс-выражение является зависимым именем, кандидатные функции находятся с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:

  • Для части поиска, использующей unqualified name lookup (3.4.1), находятся только объявления функций из контекста определения шаблона.
  • Для части поиска, использующей связные пространства имен (3.4.2), находятся только объявления функций, найденные либо в контексте определения шаблона, либо в контексте инстанциации шаблона.

В результате этого конкретное различие между static и членами анонимного пространства имен больше не существует.

0

Недавно я начал заменять статические ключевые слова на анонимные пространства имен в своем коде, но сразу столкнулся с проблемой: переменные в пространстве имен больше не были доступны для просмотра в отладчике. Я использовал VC60, поэтому не знаю, является ли это несущественной проблемой в других отладчиках. Мое решение заключалось в определении 'модульного' пространства имен, которое получало имя моего cpp файла.

Например, в моем файле XmlUtil.cpp я определяю пространство имен XmlUtil_I { ... } для всех моих модульных переменных и функций. Таким образом, я могу использовать квалификацию XmlUtil_I:: в отладчике, чтобы получить доступ к переменным. В этом случае _I отличает его от публичного пространства имен, такого как XmlUtil, которое я хотел бы использовать в других местах.

Я предполагаю, что потенциальным недостатком этого подхода по сравнению с истинно анонимным является то, что кто-то может нарушить желаемую статическую область, используя квалификатор пространства имен в других модулях. Однако не знаю, является ли это серьезной проблемой.

0

Разница заключается в именах сжатых идентификаторов (_ZN12_GLOBAL__N_11bE против _ZL1b), что, в общем-то, не имеет большого значения, но оба они собраны как локальные символы в таблице символов (из-за отсутствия директивы .global в ассемблере).

#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

Что касается вложенного анонимного пространства имен:

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

Все анонимные пространства имен первого уровня в единице трансляции объединяются друг с другом, а все вложенные анонимные пространства имен второго уровня также объединяются между собой.

Вы также можете иметь вложенное пространство имен или вложенное встроенное пространство имен в анонимном пространстве имен:

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

Для справки, это можно декодировать как:

        .data
        .align 4
        .type   (анонимное пространство имен)::A::a, @object
        .size   (анонимное пространство имен)::A::a, 4
(анонимное пространство имен)::A::a:
        .long   3

Вложенные анонимные пространства имен также могут быть встроенными, но, насколько я могу судить, inline в тогда как для анонимных пространств имен не оказывает никакого влияния.

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

Теперь что касается сжатия идентификаторов. В _ZL1b _Z означает, что это сжатый идентификатор. L указывает, что это локальный символ (через static). 1 — это длина идентификатора b, а затем сам идентификатор b.

В _ZN12_GLOBAL__N_11aE _Z также указывает на сжатый идентификатор. N показывает, что это пространство имен, 12 — длина имени анонимного пространства имен _GLOBAL__N_1, далее идет само имя анонимного пространства имен, затем 1 — длина идентификатора a, идентификатор a, и E завершает идентификатор, находящийся в пространстве имен.

В _ZN12_GLOBAL__N_11A1aE ситуация аналогична, за исключением того, что в представленной структуре имеется еще одно пространство имен (1A), которому предшествует длина A, которая равна 1. Все анонимные пространства имен имеют имя _GLOBAL__N_1.

0

Использование ключевого слова static для этой цели устарело согласно стандарту C++98. Проблема с static заключается в том, что оно не применяется к определению типа. Кроме того, это перегруженное ключевое слово, которое используется различными способами в разных контекстах, поэтому анонимные пространства имен упрощают ситуацию.

0

Согласно моему опыту, отмечу, что хотя в C++ принято помещать ранее статические функции в анонимное пространство имен, на старых компиляторах это иногда вызывает проблемы. В настоящее время я работаю с несколькими компиляторами для наших целевых платформ, и более современный компилятор для Linux вполне нормально обрабатывает функции в анонимном пространстве имен.

Однако старый компилятор, работающий на Solaris, который мы вынуждены использовать до неопределенного момента, иногда принимает это, а иногда выдает ошибку. Меня больше беспокоит не ошибка как таковая, а то, что может происходить, когда компилятор принимает это. Поэтому, пока мы не перейдем на современные технологии повсеместно, мы по-прежнему используем статические функции (обычно с областью видимости класса) там, где предпочли бы использовать анонимное пространство имен.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь