Подтвердить что ты не робот

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

В своей книге "Язык программирования С++" (третье издание) Stroustrup учит определять отдельные компоненты в своем собственном пространстве имен и импортировать их в общем пространстве имен.

Например:

namespace array_api {    
    struct array {};    
    void print(const array&) { }
}

namespace list_api {
    struct list {};        
    void print(const list&) { }
}

namespace api {
    using array_api::array;
    using list_api::list;
}

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

Почему эта техника почти никогда не используется?

4b9b3361

Ответ 1

В основном я задаюсь вопросом, какими будут преимущества (как говорит Раймонд Чен, каждая функция начинается с -100 очков). Однако у меня есть контрапункт: Luabind, который использует что-то похожее на это. См. luabind/object.hpp, в котором говорится:

namespace luabind {
  namespace adl {
    class object {
    };
  }
  using adl::object;
}

Из самого имени мы можем вывести мотивацию: поддерживать зависящий от аргументов поиск. Учитывая то, что пользователь знает как luabind::object, который на самом деле является luabind::adl::object, связанные функции будут автоматически обнаружены компилятором из пространства имен luabind::adl. Тем не менее те функции, которые пользователь может не знать о них очень четко, не "загрязняют" основное пространство имен luabind. Так что хорошо, я думаю.

Но вы знаете, что не хорошо? Вперед объявить один из этих классов. Это не удается:

namespace luabind { class object; }

Вам нужно сделать это вместо:

namespace luabind { namespace adl { class object; } }

Таким образом, абстракция быстро течет, так как пользователю все-таки нужно знать об этом магическом пространстве имен adl.

Ответ 2

Просто потому, что его никто не узнает. Большинство программистов учат ООП в стиле Java, и более распространено видеть код на С++, который инкапсулирует API-интерфейс в стиле пространства имен в class.

С++ имеет зависящий от аргумента поиск функции (ADL), который позволяет ему выбирать функцию из API на основе пространств имен типов аргументов, с которыми он звонил. Это мощный механизм, который позволяет обойти большую часть OOP-схемы, но при этом сохранить преимущества. Но это действительно не учили новичков, потому что не было особых причин.

Для чего это стоит, ваш пример примерно эквивалентен этой сокращенной версии:

namespace api {

struct array {
    friend void print(const array&) { }
};

struct list {       
    friend void print(const list&) { }
};

}

Когда вы определяете функцию как friend внутри класса, она принадлежит не классу, а охватывающему пространству имен. Более того, его имя доступно только внутри класса, а не в пространстве имен, за исключением случаев, когда он вызывал использование класса в качестве аргумента.

Загрязнение глобального пространства имен пространствами имен array_api и list_api является плохой идеей. Лучше сделать иерархию, как в моем примере.

Итак, учитывая вышеизложенное, вы можете сделать следующее:

api::array x;

print( x ); // ADL finds api::print defined inside api::array

В самом деле, так работает стиль iostream operator <<. Вам не нужно объявление using (или директива) для печати объектов из библиотеки.

Ответ 3

Я думаю, потому что это уменьшает инкапсуляцию. В вашем примере это будет правая боль, когда вы пишете map_api, чтобы перейти к api.h и импортировать его в пространство имен api. Имея один большой заголовок api.h, который импортирует каждое пространство подзаголовков, точно не включает минимизацию.

  • Map.h:

    namespace map_api { /* fns */ }
    
  • api.h:

    #include <map.h>
    namespace api { using map_api::map; }
    

Или, учитывая другие макеты заголовков: если импорт в пространство имен api происходит в другом файле:

  • map_internal.h:

    namespace map_api { /* fns */ }
    
  • map_api.h:

    #include <map_internal.h>
    namespace api { using map_api::map; }
    

Это тоже очень больно, нужно разделить api на два файла.

Последняя возможность делает все в одном файле:

  • Map.h:

    namespace map_api { /* fns */ }
    namespace api { using map_api::map; }
    

В этом случае то, что мне интересно, помещать вещи в два пространства имен, которые находятся на одном уровне абстракции, если они оба одинаково доступны для всех клиентов/включений..?

Ответ 4

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

namespace Needed {                                                                                                                                                                            
  using std::string;                                                                                                                                                                          
  using std::bind;                                                                                                                                                                            
  using std::function;                                                                                                                                                                        
  using std::cout;                                                                                                                                                                            
  using std::endl;                                                                                                                                                                            
  using namespace std::placeholders;                                                                                                                                                          
}                                                                                                                                                                                             


int main(int argc, char* argv[])                                                                                                                                                              
{                                                                                                                                                                                             

  /*  using namespace std;                                                                                                                                                                    
      would avoid all these individual using clauses,                                                                                                                     
      but this way only these are included in the global                                                                                                                                      
      namespace.                                                                                                                                                                          
  */                                                                                                                                                                                          

 using namespace Needed;  // pulls in the composition

 string s("Now I have the namespace(s) I need,");

 string t("But not the ones I don't.");

 cout << s << "\n" << t << endl;                                                                                               

 // ...

Таким образом, избегаются любые конфликты с другими частями STL, а также необходимо предусмотреть общие используемые функции с помощью std::.