2010-10-08 4 views
8

Можно создать дубликат:
Where should non-member operator overloads be placed?Перегрузка операторов и пространств имен

При просмотре на SO, я часто нахожу вопросы или ответ, который включает в себя перегружать/задающие std::ostream& operator<<(std::ostream& os, const Foo& foo) или Foo operator+(const Foo& l, const Foo& r).

Хотя я знаю, как и когда (а не) писать этих операторов, я смущен насчет вещи namespace.

Если у меня есть следующий класс:

namespace bar 
{ 
    class Foo {}; 
} 

В каком namespace я должен писать различные определения оператора?

// Should it be this 

namespace bar 
{ 
    std::ostream& operator<<(std::ostream& os, const Foo& foo); 
} 

// Or this ? 

namespace std 
{ 
    ostream& operator<<(ostream& os, const bar::Foo& foo); 
} 

// Or this ? 

std::ostream& operator<<(std::ostream& os, const bar::Foo& foo); 

Тот же вопрос относится к operator+. Итак, что такое хорошая практика здесь и почему?

+1

Дубликат [Где должен размещены не-членные перегрузки оператора?] (http: // stackoverflow.com/questions/3623631/overloading-operator) –

+0

@ Джеймс Макнеллис: Мне действительно не удалось найти аналогичный вопрос. Спасибо;) – ereOn

+1

Имейте в виду, что второе является незаконным, вы можете добавлять специализации в пространство имен 'std'. – GManNickG

ответ

9

Оно должно быть в пространстве имен bar. Вы должны рассмотреть what makes up the interface for the class и объединить их вместе.

«Класс описывает набор данных вместе с функциями, которые работают с этими данными». Ваша бесплатная функция действует на Foo, поэтому она является частью Foo. Он должен быть сгруппирован с Foo в пространстве имен bar.

Argument-dependent lookup, или ADL, найдет функцию.

Мы также знаем, что мы должны prefer non-friend non-member functions. Это означает, что в целом ваши классы будут иметь свои определения и функции-члены, а за ними сразу последуют свободные функции, которые действуют на класс.

+0

Имеет смысл и легко запоминается. По неясной причине я не рассматривал эти перегрузки как «свободные функции», которые они действительно есть. Спасибо за объяснения. – ereOn

12

Правило состоит в том, что при поиске подходящей перегрузки функций рассматриваются как текущее пространство имен, так и все пространства имен определений типов аргументов. Это называется зависимым от аргумента (ADL).

Так что, когда у вас есть этот код:

::std::ostream& os = /* something */; 
    const ::bar::Foo& foo = /* something */; 
    os << foo; 

рассматриваются следующие пространства имен:

  • Текущее пространство имен
  • :: станд, поскольку тип ОС определен там
  • :: bar, поскольку там определен тип foo

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

Однако ....

Вы не можете определить новые функции :: Std, так что вы не можете поместить свой перегруженный оператор в этом пространстве имен.(Вы можете специализировать шаблоны в :: std, но это не то, что мы здесь делаем)

Во-вторых, «текущее пространство имен» может измениться, поэтому, если вы поместите определение своей функции в это пространство имен, это может не всегда быть найденным.

Таким образом, в конце концов, самое лучшее место, чтобы поставить перегруженный оператор в том же пространстве имен Foo:

namespace bar 
{ 
    std::ostream& operator<<(std::ostream& os, const Foo& foo); 
} 
+0

Отличный и точный. Спасибо. Вот мой +1. – ereOn

0

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

namespace N 
{ 
    class X(){}; 
    void f(X){} 
} 
int main() 
{ 
    N::X x; 
    f(x); //works fine, no need to qualify f like N::f 
} 

То же самое с операторами.

С другой стороны, в случае варианта 2 оператор все еще будет найден, поскольку ostream находится в стандартном (то же правило ADL). Но не стоит добавлять материал в пространство имен std.

И третий вариант - это плохо, стилистически - зачем это делать, если первого варианта достаточно?

Так, определенно вариант 1.

HTH.

0

Хорошей практикой является объявление операторов (нечленов) в том же пространстве имен, что и класс, с которым они принадлежат.

Для чего-то вроде operator+ это довольно просто: он работает только с объектами Foo, поэтому он должен находиться в том же пространстве имен, что и сам Foo. Для operator<< и operator>> вы все равно можете выбрать между пространствами имен std и bar. Прежде всего, вы не должны добавлять перегрузки функций/операторов в пространство имен std. Во-вторых, важная часть этих перегрузок заключается не в том, что они работают с потоком, а в том, что они читают/записывают объект Foo. Поэтому имеет смысл объединить его с классом Foo.

Следует также отметить, что правила C++ разработаны таким образом, что перегруженные операторы, которые определены в том же пространстве имен, что и класс, на котором они работают, будут почти всегда правильно найдены, а это будет происходить неправильно гораздо чаще, если операторы объявляются в некотором другом, несвязанном пространстве имен.

2

Для корректной работы перегрузки оператора функция должна быть в том же пространстве имен, что и один из его операндов. В противном случае ADL не находит его. Это означает пространство имен вашего класса для операторов , таких как + и -. Теоретически вы могли бы поставить оператор < < в std или в том же пространстве имен, что и ваш класс, но стандарт запрещает определять новые функции в std, так что и здесь вы помещаете его в то же пространство имен, что и класс.

(И, конечно, вы обычно не реализуют + или -, а + = и - =, а затем извлечь из шаблона, который обеспечивает + и -. Автоматически)