Пространство имен класса VERSUS, пространство имен OR и AND?
Оба класса и пространства имен?
Этот вопрос касается шаблона, который, как я вижу, я использую все больше и больше: наличие как класса, так и пространства имен для связанных понятий. Я думаю, что это мотивировано главным образом артефактами языка С++, но не полностью.
Я полагаю, что вопрос верхнего уровня: это хорошая идея? Имея как класс, так и пространство имен для связанных понятий?
Вопрос нижнего уровня:
Каков наилучший способ сделать это?
Класс, вложенный в пространство имен?:
namespace Foo_Namespace {
class Foo_Class {
...
};
}
Или отдельный, одноранговый, класс и пространство имен?:
class Foo_Class {
...
};
namespace Foo_Namespace {
// non-class free functions, etc.
}
Я должен признать, что я склоняюсь к вложению класса в пространство имен.
Хотя это приводит к уродливым именам.
Но даже если я это сделаю, какие соглашения об именах следует использовать:
Слишком длинное, что приводит к действительно уродливым именам Foo_Namespace:: Foo_Class
namespace Foo_Namespace {
class Foo_Class {
...
};
}
Нет необходимости использовать суффиксы или индикаторы в имени:
namespace Foo {
class Foo {
...
};
}
Но тогда я нахожу себя неуверенным, когда смотрю на Foo:: bar(), если это бесплатная функциональная панель в пространстве имен:: Foo, т.е.:: Foo:: bar() или функция-член в классе Foo в пространстве имен:: Foo:: Foo:: bar().
И имена вроде:: Foo:: Foo:: bar по-прежнему не являются, umm, nice.
В настоящее время я делаю
Нет необходимости использовать суффиксы или индикаторы в имени:
namespace Foo_ns {
class Foo {
...
};
}
главным образом потому, что я обычно сначала создаю класс, а потом понимаю, что пространство имен будет приятным.
Интересно, если я должен возродить соглашение об именах, которое я не использовал в годах: _c для класса, _ns для пространств имен:
namespace Foo_ns {
class Foo_c {
...
};
}
Детали:
Я не буду повторять то, что я сказал выше, но я добавлю немного больше.
Самая практическая причина, по которой я знаю, использовать пространство имен в дополнение к классу, заключается в том, что вам разрешено отправлять декларации свободных функций в пространстве имен, но вам не разрешено делать пересылку объявлений некоторых методов класса, То есть с классом вы должны объявить все или ничего. В то время как со свободными функциями вообще и свободными функциями в пространствах имен, в частности, вы можете объявить это по частям. Части могут быть объявлены в разных файлах заголовков. Вместо #, включая массивную библиотеку только для заголовка для массивного класса, вы можете использовать форвардные объявления только для одной или двух функций. Etc.
(см., например, http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Forward_Declarations, хотя Google беззаботно отказывается от форвардных деклараций, а не включает заголовок.)
Другая причина заключается в том, что пространство имен позволяет сохранить класс в целом.
Самое большое преимущество для класса состоит в том, что класс может быть передан шаблону, тогда как пространство имен не может быть.
Я предпочитаю, чтобы класс, вложенный в пространство имен, Foo_ns/Foo_ns:: Foo_c, вместо того, чтобы иметь их как peer Foo_ns/Foo_c, потому что часто классу нужны вспомогательные классы, например. Foo_ns/Foo_ns:: Foo_c/Foo_ns:: Foo_Helper_c. Если класс и пространство имен являются одноранговыми, кажется странным иметь Foo_ns/Foo_c, но Foo_ns:: Foo_Helper_c.
Мне нравятся пространства имен, потому что я согласен с Андреем Александресю: http://laser.inf.ethz.ch/2012/slides/Alexandrescu/1-C++%20course%20parts%201%20and%202.pdf
Class methods vs. free functions
• Conventional wisdom: methods are cool, free functions are so 1960s
• Yet:
◦ Free functions improve encapsulation over methods
◦ Free functions may be more general
◦ Free functions decouple better
◦ Free functions support conversions on their left-hand argument
Surprising fact #2
Making a function a method should be
your last, not first, choice
(c) 2012– Andrei Alexandrescu. 32 / 59
Лучше создавать свободные функции, чем методы в классах.
Но иногда нужно просто использовать классы - например, для шаблонов.
Соглашение об именах мудрый
Я использовал Foo_ns/Foo_ns:: Foo_c в прошлом
Я использую Foo_ns/Foo_ns:: Foo now
(Кстати, я предпочитаю использовать Class_Names_With_Underscores_and_Initial_Caps, а не CamelCase.)
Если это имеет смысл, я могу исключить суффикс _ns в пространстве имен - например. где пространство имен и класс не должны иметь одинаковое имя.
Мне не нравится, что они имеют одно и то же имя. Рассмотрим конструктор для класса Foo внутри пространства имен foo:
:: Foo:: Foo:: Foo() против :: Foo_ns:: Foo:: Foo()
Последнее не намного лучше, но немного запутаннее.
Я думаю, что я обычно создаю класс сам по себе, не вложив его в пространство имен.
Фактически, я, вероятно, добавляю несколько статических методов, прежде чем я пойму, что класс, вложенный в пространство имен, будет лучше. На этом этапе это может быть болью для рефакторинга, и иногда я создаю функции пересылки, которые переходят от статического метода класса к свободной функции в пространстве имен или наоборот. Это заставляет меня сожалеть о боте, чтобы перейти к классу с пространством имен прямо с шага 1.
Заключение
Мой текущий BKM - Foo_ns/Foo_ns:: Foo, т.е.
namespace Foo_ns {
class Foo {
...
};
}
Буду признателен за любые предложения, любые улучшения.
Или я просто сломался для этого?
Ответы
Ответ 1
Я рекомендую иметь ваш класс в пространстве имен со связанными функциями. Большая причина для этого - зависимый от аргумента поиск (ADL). Когда вы вызываете функцию, отличную от члена, которая имеет аргумент типа класса, имя функции просматривается в охватывающем пространстве имен этого класса. Поэтому, если у вас есть, скажите:
namespace foo {
class bar { };
void baz(bar);
}
Если вы когда-нибудь захотите позвонить baz
, вам не нужно явно указывать пространство имен, в котором оно содержится, вы можете просто сделать:
foo::bar x;
baz(x);
Без квалификации baz
компилятор все еще нашел функцию, потому что она находится в пространстве имен, которое охватывает тип его аргумента. Таким образом, С++ рассматривает содержимое охватывающего пространства имен класса как часть интерфейса этого класса. Внедрение функций, которые являются частью интерфейса класса как функции, не входящие в него, таким образом, позволяя ADL находить функцию, увеличивает инкапсуляцию. Если функции не требуется доступ к внутренним элементам класса, не делайте ее членом этого класса - вместо этого помещайте ее в то же пространство имен, что и этот класс.
Однако подозрительно, что ваше пространство имен имеет то же имя, что и ваш класс. Обычно в пространстве имен будет более одного класса, и даже если бы их не было, имена были бы разными. Имя пространства имен должно описывать его содержимое, а не только один класс внутри него.
Ответ 2
Отдельно Foo_Class
и Foo_Namespace
определенно неверно, это не позволит вам использовать зависящий от аргумента поиск, чтобы найти функции в пространстве имен, которые предназначены для использования с классом.
Таким образом, класс должен быть вложен в пространство имен, если в пространстве имен будут присутствовать функции, принимающие аргументы типа класса.
Использование одного и того же имени для класса и пространства имен немного запутанно, что может привести к неоднозначности в некоторых случаях.
Также необычно назвать пространство имен с начальной буквой в верхнем регистре. Если имена классов начинаются с прописной буквы, вы можете перейти на пространство имен foo
и класс foo
, указав foo::Foo
.
Не будет ли пространство имен содержать более одного класса? Это звучит необычно для меня. Я бы назвал пространство имен после всего его содержимого, просто не одного типа. Если это класс socket
, я бы поставил его в пространство имен networking
, например.
Я думаю, что суффиксы _c
на классах совершенно смехотворны. Мне не нравится суффикс _ns
на пространствах имен, его единственное сохранение - это то, что Foo_ns::Foo
лучше, чем foo::Foo
, но я все равно сделаю его foo::Foo
.
(Остальная часть вашего вопроса просто кажется "почему хорошие пространства имен", которые не нуждаются в пояснении, они были добавлены на язык по какой-либо причине, и хороший совет по использованию нечленов для частей интерфейс, который не обязательно должен быть членом. Google прав, в основном используют заголовки, не пересылающие декларации, что не изменяет того факта, что вы можете повторно открыть пространства имен в отдельных заголовках, чтобы добавить новые имена в пространство имен, преимущество, которое вы описываете.)
Ответ 3
Мне не нравятся "фрактальные" имена, повторяющие вещи и пытающиеся избежать этого. Для вашего случая я бы попробовал что-то вроде пространства имен "Foo" и вложенного класса "Тип". Возможно, какое-то другое имя было бы более подходящим, но это зависит от вашего фактического использования. Вот пример использования, который я повторяю снова и снова:
namespace status {
enum type {
invalid,
okay,
on_fire
};
// debug output helper
char const* to_string(type t);
}
Тип перечисления называется status::type
, который может принимать значения, такие как status::okay
. Довольно читаемый ИМХО.