Правильный способ определения методов пространства имен C ++ в файле .cpp

108

Вероятно, дубликат, но его нелегко найти ...

Учитывая заголовок вроде:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

Я видел method()несколько способов определения в файле .cpp:

Версия 1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

Версия 2:

using namespace ns1;

void MyClass::method()
{
 ...
}

Версия 3:

void ns1::MyClass::method()
{
 ...
}

Есть ли «правильный» способ сделать это? Что-то из этого «неправильное» в том, что все они не означают одно и то же?

Мистер мальчик
источник
В большинстве кодов я обычно вижу третью версию (но я не знаю почему: D), вторая версия просто противоположна тому, почему вводятся пространства имен, я думаю.
Мистер Анубис
1
Что интересно, инструменты рефакторинга Visual Assist с удовольствием генерируют код, используя №1 или №3, в зависимости от того, какой стиль уже используется в целевом файле.
Mr. Boy

Ответы:

51

Версия 2 неясна и непроста для понимания, потому что вы не знаете, к какому пространству имен MyClassпринадлежит, и это просто нелогично (функция класса не в том же пространстве имен?)

Версия 1 верна, потому что она показывает, что в пространстве имен вы определяете функцию.

Версия 3 верна еще и потому, что вы использовали ::оператор разрешения области видимости для ссылки на MyClass::method ()в пространстве имен ns1. Я предпочитаю версию 3.

См. Пространства имен (C ++) . Это лучший способ сделать это.

GILGAMESH
источник
22
Назвать № 2 «неправильным» - это огромное преувеличение. По этой логике все имена символов являются «неправильными», потому что они потенциально могут скрыть другие имена символов в других областях.
tenfour,
Это нелогично. Он будет компилироваться нормально (извините, неверно объяснил это в ответе), но зачем вам определять функцию вне ее пространства имен? Это сбивает читателя с толку. Кроме того, когда используется много пространств имен, он не показывает, к какому пространству имен принадлежит MyClass. В версиях 1 и 3 эта проблема устранена. В заключение, это не неправильно, а просто непонятно и запутанно.
GILGAMESH
3
Я согласен с @PhoenicaMacia, трюк с использованием ужасен и может привести к путанице. Рассмотрим класс, который реализует оператор как бесплатную функцию, в заголовке, который у вас будет namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }, теперь в файле cpp с помощью оператора using вы можете определить функцию-член как void X::f() {}, но если вы определите, X operator==(X const&, X const&)вы будете определять другой оператор, отличный от определено в заголовке (вам нужно будет использовать там 1 или 3 для бесплатной функции).
Дэвид Родригес - дрибес
1
В частности, я предпочитаю 1, а пример в связанной статье на самом деле ничего не решает, в первом примере используется 1, во втором используется сочетание 1 и 3 (функции определены с квалификацией, но они определены внутри внешнего пространства имен)
Дэвид Родригес - дрибес
1
Из трех я бы сказал, что 1) является лучшим, однако у большинства IDE есть довольно неприятная привычка делать отступы внутри этого объявления пространства имен, и это действительно добавляет путаницы.
locka
28

5 лет спустя, и я подумал, что упомяну об этом, что и выглядит красиво, и не является злом

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Пузомор Хорватия
источник
3
Это лучший ответ. Он выглядит наиболее чистым и позволяет избежать проблем с OP версии 1, которые могут непреднамеренно переносить вещи в пространство имен, и 2, которые могут непреднамеренно переносить вещи в глобальное пространство.
ayane_m
Да, это отличная комбинация меньшего количества наборов текста, чем 3, но при этом явного объявления намерения.
jb
14

Я использую версию 4 (ниже), потому что она сочетает в себе большинство преимуществ версии 1 (краткость резонансного определения) и версии 3 (быть максимально явным). Главный недостаток в том, что люди не привыкли к нему, но, поскольку я считаю, что это технически лучше альтернатив, я не возражаю.

Версия 4: используйте полную квалификацию с использованием псевдонимов пространств имен:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

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

Дитмар Кюль
источник
1
Хотя я думаю , что «это лучше , так что я не волнует , если это сбивает с толку людей , » логика ошибочна, я должен согласиться , что это является хорошим подходом для вложенных пространств имен.
Mr. Boy
1
+1 за отличную идею «почему я не подумал об этом»! (Что касается «люди не привыкли к [новым технически совершенным вещам]», они привыкнут к этому, если так будет делать больше людей.)
wjl
Просто чтобы убедиться , что я понимаю, оба outerи innerопределяется как пространство имен в других файлах заголовков уже?
dekuShrub
4

Версия 3 делает связь между классом и пространством имен очень явной за счет большего количества типов. Версия 1 избегает этого, но фиксирует связь с блоком. Версия 2 имеет тенденцию скрывать это, поэтому я бы избегал этого.

Пол Джойрман
источник
3

Я выбираю Num.3 (также известную как подробный вариант). Это больше похоже на набор текста, но намерение точно для вас и для компилятора. Проблема, которую вы разместили как есть, на самом деле проще, чем в реальном мире. В реальном мире существуют другие области для определений, а не только члены класса. Ваши определения не очень сложны только с классами - потому что их область действия никогда не открывается повторно (в отличие от пространств имен, глобальной области и т. Д.).

Num.1 это может потерпеть неудачу с областями, отличными от классов - все, что можно открыть повторно. Таким образом, вы можете объявить новую функцию в пространстве имен, используя этот подход, или ваши встроенные строки могут быть заменены через ODR. Это понадобится вам для некоторых определений (особенно для специализаций шаблонов).

Num.2 Это очень хрупко, особенно в больших базах кода - по мере изменения заголовков и зависимостей ваша программа не сможет компилироваться.

Num.3 Это идеальный вариант, но нужно много набирать - какова ваша цель - что-то определить . Это делает именно это, и компилятор подключается, чтобы убедиться, что вы не сделали ошибки, определение не рассинхронизировано с его объявлением и т. Д.

Джастин
источник
3

Оказывается, дело не только в «стиле кодирования». Num. 2 приводит к ошибке связывания при определении и инициализации переменной, объявленной как extern в заголовочном файле. Взгляните на пример в моем вопросе. Определение константы в пространстве имен в файле cpp

джакумате
источник
2

Все способы верны, и у каждого есть свои преимущества и недостатки.

В версии 1 у вас есть преимущество в том, что вам не нужно писать пространство имен перед каждой функцией. Недостатком является то, что вы получите скучную идентификацию, особенно если у вас более одного уровня пространств имен.

В версии 2 вы делаете свой код более чистым, но если у вас есть несколько пространств имен, реализованных в CPP, одно может напрямую обращаться к функциям и переменным другого, что делает ваше пространство имен бесполезным (для этого файла cpp).

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

Есть и другой способ его использования. Он похож на первую версию, но без проблем с идентификацией.

Это вот так:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

Вам решать, что лучше для каждой ситуации =]

Ренан Грайнерт
источник
14
Я не вижу смысла использовать здесь макрос. Если вы не хотите делать отступы, просто не делайте отступов. Использование макроса делает код менее очевидным.
tenfour,
1
Я думаю, что последняя версия, которую вы упомянули, полезна, когда вы хотите скомпилировать свой код со старыми компиляторами, которые не поддерживают пространства имен (да, некоторые динозавры все еще существуют). В этом случае вы можете поместить макрос в #ifdefпредложение.
Лука Мартини
Вам не нужно идентифицировать, если вы не хотите, но если вы не используете макросы, некоторые IDE попытаются сделать это за вас. Например, в Visual Studio вы можете выделить весь код и нажать клавиши ALT + F8 для автоматической идентификации. Если вы не используете определения, вы потеряете эту функциональность. Кроме того, я не думаю, что OPEN_ (пространство имен) и CLOSE_ (пространство имен) менее очевидны, если они у вас есть в вашем стандарте кодирования. Причина, которую дал @LucaMartini, тоже интересна.
Ренан Грайнерт,
Если бы это было сделано общим, то есть #define OPEN_NS(X)я думаю, что это немного полезно, но не совсем ... Я не возражаю против макросов, но это похоже на OTT. Я думаю, что подход Дитмара Кюля лучше подходит для вложенных пространств имен.
Mr. Boy