Почему используются безымянные пространства имен и каковы их преимущества?


242

Я только что присоединился к новому программному проекту C ++, и я пытаюсь понять дизайн. Проект часто использует безымянные пространства имен. Например, что-то подобное может произойти в файле определения класса:

// newusertype.cc
namespace {
  const int SIZE_OF_ARRAY_X;
  const int SIZE_OF_ARRAY_Y;
  bool getState(userType*,otherUserType*);
}

newusertype::newusertype(...) {...

Каковы конструктивные соображения, которые могут привести к использованию безымянного пространства имен? Какие преимущества и недостатки?

Ответы:


189

Безымянные пространства имен - это утилита, позволяющая сделать единицу перевода идентификатора локальной. Они ведут себя так, как будто вы выбираете уникальное имя для каждой единицы перевода для пространства имен:

namespace unique { /* empty */ }
using namespace unique;
namespace unique { /* namespace body. stuff in here */ }

Дополнительный шаг с использованием пустого тела важен, поэтому вы уже можете ссылаться в теле пространства имен на идентификаторы, подобные ::nameтем, которые определены в этом пространстве имен, поскольку директива using уже имела место.

Это означает, что вы можете вызывать бесплатные функции (например), helpкоторые могут существовать в нескольких единицах перевода, и они не будут конфликтовать во время ссылки. Эффект почти идентичен использованию staticключевого слова, используемого в C, которое вы можете вставить в объявление идентификаторов. Неназванные пространства имен - превосходная альтернатива, позволяющая даже сделать единицу перевода типа локальной.

namespace { int a1; }
static int a2;

Оба aявляются локальными единицами перевода и не будут конфликтовать во время ссылки. Но разница в том, что a1в анонимном пространстве имен получается уникальное имя.

Прочитайте отличную статью на comeau-computing. Почему вместо статического используется безымянное пространство имен? ( Архив Archive.org ).


Вы объясняете отношение к static. Можете ли вы также сравнить с __attribute__ ((visibility ("hidden")))?
Финз

74

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

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

Все анонимные пространства имен в одном и том же файле рассматриваются как одно и то же пространство имен, и все анонимные пространства имен в разных файлах различны. Анонимное пространство имен эквивалентно:

namespace __unique_compiler_generated_identifer0x42 {
    ...
}
using namespace __unique_compiler_generated_identifer0x42;

14

Безымянное пространство имен ограничивает доступ класса, переменной, функции и объектов к файлу, в котором он определен. Безымянная функциональность пространства имен похожа на staticключевое слово в C / C ++.
staticКлючевое слово ограничивает доступ глобальной переменной и функции к файлу, в котором они определены.
Существует различие между безымянным пространством имен и staticключевым словом, из-за которого безымянное пространство имен имеет преимущество перед статическим. staticКлючевое слово может использоваться с переменной, функцией и объектами, но не с определенным пользователем классом.
Например:

static int x;  // Correct 

Но,

static class xyz {/*Body of class*/} //Wrong
static structure {/*Body of structure*/} //Wrong

Но то же самое может быть возможно с безымянным пространством имен. Например,

 namespace {
           class xyz {/*Body of class*/}
           static structure {/*Body of structure*/}
  } //Correct

13

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

Например, в моей системе следующий код занимает около 70% времени выполнения, если используется анонимное пространство имен (x86-64 gcc-4.6.3 и -O2; обратите внимание, что дополнительный код в add_val заставляет компилятор не хотеть включать это дважды).

#include <iostream>

namespace {
  double a;
  void b(double x)
  {
    a -= x;
  }
  void add_val(double x)
  {
    a += x;
    if(x==0.01) b(0);
    if(x==0.02) b(0.6);
    if(x==0.03) b(-0.1);
    if(x==0.04) b(0.4);
  }
}

int main()
{
  a = 0;
  for(int i=0; i<1000000000; ++i)
    {
      add_val(i*1e-10);
    }
  std::cout << a << '\n';
  return 0;
}

5
Слишком хорошо, чтобы быть правдой - я попробовал этот сегмент на gcc 4-1-2, используя оптимизацию O3, с оператором пространства имен и без него: -> Получил одно и то же время (3 секунды, с -O3 и 4 секунды с -O3)
Тео

2
Этот код был намеренно сложным, чтобы попытаться убедить компилятор не вставлять b и add_val в main. Оптимизация O3 использует много встраивания независимо от затрат на раздувание кода. Однако все еще существуют вероятные функции, в которых O3 не встроил бы add_val. Вы можете попытаться сделать add_val более сложным или вызвать его несколько раз из main в разных обстоятельствах.
xioxox

5
@Daniel: что я пропускаю? как вы прочитали, вы сказали, что сравнили -O3с самим собой, затем вы сказали, что 3 против 4 секунд "то же самое время". ни один из них не имеет смысла. Я подозреваю, что реальное объяснение будет, но что это?
underscore_d

@underscore_d Ответ о состоянии -O2 использовался в обоих случаях, а не -O3. Различные уровни оптимизации могут вести себя по-разному. Кроме того, разные версии компилятора могут вести себя по-разному (ответ может быть устаревшим, то есть)
Пол Стелян

1
@PaulStelian Я знаю это, но кажется довольно ясным, что я отвечал не на ответ xioxox, а скорее на комментарий Тео (хотя либо его имя изменилось, либо я как-то перепутал)
underscore_d

12

Пример показывает, что люди в проекте, к которому вы присоединились, не понимают анонимных пространств имен :)

namespace {
    const int SIZE_OF_ARRAY_X;
    const int SIZE_OF_ARRAY_Y;

Они не обязательно должны находиться в анонимном пространстве имен, поскольку constобъект уже имеет статическую связь и, следовательно, не может конфликтовать с идентификаторами с тем же именем в другом модуле перевода.

    bool getState(userType*,otherUserType*);
}

И это на самом деле пессимизация: getState()имеет внешнюю связь. Обычно лучше использовать статическую связь, так как это не загрязняет таблицу символов. Лучше написать

static bool getState(/*...*/);

Вот. Я попал в ту же ловушку (в стандарте есть формулировка, предполагающая, что статика файлов как-то устарела в пользу анонимных пространств имен), но работая в большом проекте на C ++, таком как KDE, вы получаете множество людей, которые правильно поворачивают голову снова вокруг :)


10
Поскольку в безымянных пространствах имен c ++ 11 есть внутренняя связь (раздел 3.5 стандарта или en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces )
Эмиль Врийдагс,

11
«Они не должны быть в анонимном пространстве имен». Технически, конечно, но все же не мешало бы поместить их в одно, как визуальное напоминание об их семантике и сделать ее (даже более) тривиальной, чтобы удалить constness позже при желании. Я сомневаюсь, что это означает, что команда ОП "ничего не понимает"! Также, как отмечалось, в C ++ 11 и далее неправильно рассказывается о функциях в анонимных пространствах имен, имеющих внешнюю связь. Насколько я понимаю, они устранили проблему с аргументами шаблона, ранее нуждающимися во внешней связи, поэтому могли позволить безымянным пространствам имен (способным содержать аргументы шаблона) иметь внутреннюю связь.
underscore_d

11

Анонимное пространство имен делает вложенные переменные, функции, классы и т. Д. Доступными только внутри этого файла. В вашем примере это способ избежать глобальных переменных. Нет разницы во времени выполнения или компиляции.

Существует не так много преимуществ или недостатков, кроме "хочу ли я, чтобы эта переменная, функция, класс и т. Д. Были открытыми или закрытыми?"


2
Могут быть различия в производительности - см. Мой ответ здесь. Это позволяет компилятору оптимизировать код лучше.
xioxox

2
У вас есть пункт; по крайней мере, насколько C ++ сегодня. Тем не менее, C ++ 98 / C ++ 03 требует, чтобы вещи имели внешнюю связь для использования в качестве аргументов шаблона. Так как вещи в анонимных пространствах имен доступны в качестве аргументов шаблона, они будут иметь внешнюю связь (по крайней мере, до C ++ 11), даже если бы не было возможности ссылаться на них извне файла. Я думаю, что, возможно, была какая-то способность обдумывать это, потому что стандарт требует, чтобы все действовало так, как если бы правила применялись; и иногда это можно сделать без реального соблюдения правил.
Макс Либберт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.