Безымянное пространство имен в именованном пространстве имен

Некоторый код, который меня попросили изменить, выглядит примерно так:

namespace XXX {

namespace {

// some stuff

} // end of unnamed

// Some stuff within the scope of XXX

} // end of XXX

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

namespace {

// some stuff

} // end of unnamed

namespace XXX {

// Some stuff within the scope of XXX

} // end of XXX

Любые мнения будут с благодарностью.

5 ответов

Решение

Ладно получается X::<anonymous>::foo() виден какX::foo(), Я удивлен.

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


Оригинальный ответ

Ну, это скорее зависит от "материала", не так ли?

Существующий код позволяет код в X иметь "частные" другие вещи, которые также находятся в X но не могут быть доступны извне X:

#include <iostream>

namespace X {
   namespace {
      void foo() { std::cout << "lol\n"; }
   }

   void bar() { foo(); }
}

int main()
{
   X::bar();
   // X::foo();  // can't do this directly  [edit: turns out we can!]
}
  • Выход: lol\n

Ваш предложенный подход делает этот "частный материал" доступным для всего блока перевода:

#include <iostream>

namespace {
   void foo() { std::cout << "lol\n"; }
}

namespace X {
   void bar() { foo(); }
}

int main()
{
   X::bar();
   foo();     // works
}
  • Выход: lol\nlol\n

Это имеет практическую пользу. Неназванное пространство имен скрывает внутри него имена от разных единиц перевода.

Приведенный выше код работает только потому, что определение foo находится в той же единице перевода.

Предположим, что main() и определение foo() находятся в разных единицах перевода. Он будет скомпилирован, так как основной файл содержит заголовок объявления. но он не будет связывать, потому что логически нет такой вещи как X::(безымянное пространство имен)::foo.

С глобальной точки зрения очень мало пользы: если смотреть с точки зрения других единиц перевода, то оба подхода дают одинаковые результаты: анонимное пространство имен невидимо (или на него нельзя ссылаться).

С той же точки зрения единицы перевода есть различие: тот факт, что вы определяете пространство имен верхнего уровня, означает, что вы уменьшаете вероятность импорта конфликта пространства имен, объявленного в другом месте, и наиболее распространенным является глобальное пространство имен (namespaceless функции, подумайте о чем-нибудь, унаследованном от ISO C, как stdio.h или что-то еще).

Например, если глобальный заголовок, который вы импортируете в этом модуле перевода, имеет abort() без пространства имен, и вы объявляете пространство имен { abort() { ...} } в своем модуле перевода, у вас будет неоднозначность, например, gcc выдаст ошибку компиляции:

error: call of overloaded ‘abort()’ is ambiguous

Теперь, если вы называете анонимное пространство имен внутри именованного пространства имен, вы получаете следующие эффекты:

а) нет никакой двусмысленности для функций, объявленных внутри пространства имен, потому что это имеет приоритет:

namespace a { namespace { abort() {...} } }

если у вас есть функция, подобная a::what(), и она ссылается на abort(), она разрешается в своем собственном пространстве имен, так как имеет приоритет

б) У вас не будет глобальной связи для a:: abort(), так как она не существует вне модуля перевода, так же, как namespace { abort(); } на верхнем уровне, но без потенциального конфликта выше.

И в "b" заключается разница: это не то же самое, что просто пространство имен a { abort(); } потому что он не будет иметь глобальной связи, поэтому вы можете переопределить его в другом модуле перевода без конфликтов. Удачи в попытке связать два модуля перевода, которые оба определяют пространство имен a { abort() { ... } } ...

Таким образом, вы получите именно то, что вы имеете в виду:

namespace a { // you have a named space, so you don't have conflicts with the nameless one
  namespace { // but you have local visibility and linkage
    whatever(); // for this
  }
}

Короче говоря: оба способа имеют сходство, но есть разница. Можно поспорить, что это не очень полезно, но в качестве стиля он будет избегать столкновений с глобальным пространством имен. Можно все еще утверждать, что, поскольку они будут обнаружены во время компиляции (надеюсь, по крайней мере, когда подписи будут совпадать идеально), зачем беспокоиться. Но это полезная концепция, если ваш проект представляет собой библиотеку, предназначенную для переноса, и ваши заголовки могут быть загрязнены в зависимости от того, какую среду сами заголовки среды импортируют, так как в противном случае ваши пользователи должны будут пропатчить вашу библиотеку для своих систем, или вам понадобится #ifdefs здесь и там.

Я много программирую на ISO/ANSI C 99 и время от времени мне приходится делать такие вещи, как:

#include <headerA.h>
#define symbol symbolB
#include <headerB.h>
// or some crap alike. And I have linker problems with above.

... потому что оба заголовка (например, из разных библиотек) могут загрязнять пространство имен, и я не могу просто исправить чужую библиотеку.

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

Одним из преимуществ использования безымянного пространства имен внутри именованного пространства имен является предотвращение зависимого от аргумента поиска (ADL). Действительно, если вы определяете типы и функции в одном и том же именованном пространстве, вы можете столкнуться с нежелательными эффектами. Следующий пример иллюстрирует проблему:

Скажем, у нас есть простой файл заголовка, как показано ниже, где мы определяем общую функцию myfunction для нашего нестандартного типа mytype внутри нашего пространства имен mynamespace.

//header.h
namespace mynamespace {
    struct mytype {};

    static void myfunction(mytype){}
}

Теперь внутри другого файла test.cpp, мы хотим переопределить myfunctionдля конкретной цели (например, распечатать отладочную информацию, сделать дополнительные вещи, I18N...), не изменяя остальную часть нашей программы. Следующий код не будет конфликтовать с нашим файлом заголовка, потому что это другое пространство имен, то есть глобальное пространство имен.

//test.cpp
#include "header.h"

static void myfunction(mynamespace::mytype){}

int main(){
    mynamespace::mytype val = {};
    //error: call of overloaded 'myfunction(mynamespace::mytype&)' is ambiguous
    myfunction(val);
}

Удивительно, но звонок myfunctionвыше неоднозначно и не компилируется из-за ADL. Чтобы решить вашу проблему, вы можете предотвратить ADL благодаря анонимному пространству имен.

//header.h
namespace mynamespace {
    struct mytype {};

    //OK prevents ADL
    namespace {
        static void myfunction(mytype){}
    }
}

Простое преимущество заключается в том, что из анонимного пространства имен внутри именованного пространства имен можно получить прямой доступ к элементам именованного пространства имен:

      namespace foo {
    class Bar;

    namespace {
        void Baz() {
            Bar bar;  // no need to specify the foo namespace 
            …
        }
    }

    …
}

вместо:

      class foo::Bar;

namespace {
    void Baz() {
        foo::Bar bar;
        …
    }
}

namespace foo {
    …
}
Другие вопросы по тегам