Почему композиция пространства имен так редко используется?
В своей книге "Язык программирования С++" (третье издание) 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;
}
Я выгляжу интересным, но я никогда не видел, чтобы этот подход использовался на практике.
Почему эта техника почти никогда не используется?
Ответы
Ответ 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, который импортирует каждое пространство подзаголовков, точно не включает минимизацию.
Или, учитывая другие макеты заголовков: если импорт в пространство имен api происходит в другом файле:
Это тоже очень больно, нужно разделить api на два файла.
Последняя возможность делает все в одном файле:
В этом случае то, что мне интересно, помещать вещи в два пространства имен, которые находятся на одном уровне абстракции, если они оба одинаково доступны для всех клиентов/включений..?
Ответ 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::
.