Функция скрытия и использования-объявления в С++

Моя путаница происходит из раздела "С++ Primer 5th edition" 13.3, стр. 518.

Очень осторожные читатели могут задаться вопросом, почему декларация using внутри swap не скрывает объявления для HasPtr версии swap.

Я попытался прочитать его ссылку, но до сих пор не понял почему. Может ли кто-нибудь объяснить это немного, пожалуйста? Благодарю. Вот пример кода вопроса.

Предположим, что класс Foo имеет член с именем h, который имеет тип HasPtr.

void swap(HasPtr &lhs, HasPtr &rhs)
{...}

void swap(Foo &lhs, Foo &rhs)
{
    using std::swap;
    swap(lhs.h, rhs.h);
}

Почему swap для HasPtr не скрыт, который, кажется, объявлен во внешней области, а using std::swap находится во внутренней области? Спасибо.

Ответы

Ответ 1

Потому что using std::swap; не означает "отныне, каждый" обмен "должен использовать std::swap", но "переносит все перегрузки swap из std в текущую область действия".

В этом случае эффект такой же, как если бы вы написали using namespace std; внутри функции.

Ответ 2

Объявление-использование using std::swap не скрывает функции, которые вы объявили, для обмена HasPtr и Foo. Это имя swap формирует пространство имен std в декларативной области. При этом std::swap может принимать участие в разрешении перегрузки.

Из стандарта С++ 11:

7.3.3 Объявление использования

1 Использование-декларация вводит имя в декларативный регион, в котором появляется декларация использования.

using-declaration:

using typename opt идентификатор nested-name-unqualified-id;
   using :: unqualified-id;

Имя участника, указанное в объявлении using, объявляется в декларативной области, в которой появляется декларация использования. [Примечание: объявлено только указанное имя; указание имени перечисления в декларации использования не объявляет его счетчики в декларативной области using-declarations. -end note] Если декларация using называет конструктор (3.4.3.1), он неявно объявляет набор конструкторов в классе, в котором появляется декларация использования (12.9); в противном случае имя, указанное в объявлении using, является синонимом имени какого-либо объекта, объявленного в другом месте.

В вашем случае у вас есть:

void swap(Foo &lhs, Foo &rhs)
{
    using std::swap;
    swap(lhs.h, rhs.h);
}

Объявление using std::swap; вводит имя swap из пространства имен std в декларативную область, которая является телом функции swap(Foo&, Foo&). Имя swap из глобального пространства имен все еще доступно в теле функции.

Если то, что вы разместили, является полнотой функции, тогда вам не нужно объявление using std::swap. Вы могли бы просто:

void swap(Foo &lhs, Foo &rhs)
{
    swap(lhs.h, rhs.h);
}

так как swap(HasPtr &lhs, HasPtr &rhs) видно из функции.

Теперь взглянем на пример ниже.

struct Bar {};

void swap(Bar& lhs, Bar& rhs)
{
}

struct Foo
{
   int a;
   Bar b;
};

void swap(Foo& lhs, Foo& rhs)
{
   swap(lhs.a, rhs.a);  // A problem
   swap(lhs.b, rhs.b);
}

Линия, помеченная A problem, является проблемой, так как нет функции с именем swap, которая может работать с двумя объектами типа int& в качестве аргумента. Вы можете исправить это, используя один из следующих способов:

  • Использовать std::swap явно.

    void swap(Foo& lhs, Foo& rhs)
    {
       std::swap(lhs.a, rhs.a);
       swap(lhs.b, rhs.b);
    }
    
  • Ввести функцию std::swap в функцию.

    void swap(Foo& lhs, Foo& rhs)
    {
       using std::swap;
       swap(lhs.a, rhs.a);
       swap(lhs.b, rhs.b);
    }
    

Ответ 3

Эффективное С++ Третье издание Скотта Мейерса (Пункт 24)

Когда компиляторы видят вызов для свопинга, они ищут правильный своп вызов. Правила поиска имен в С++ гарантируют, что это найдет T-специфический обмен в глобальном масштабе или в том же пространстве имен, что и тип Т.

В этом случае и во втором блоке кода компиляторы ищут смену HasPtr, и если они их не найдут, они возвращаются к общей версии в std.

Ответ 4

Объявление использования означает рассмотрение всех перегрузок std:: swap, как если бы они были определены в том же пространстве имен, что и функция. Поскольку декларация использования появляется в теле функции, это временно действует только в пределах этой области.

Он идентичен по существу следующим образом:

void swap(HasPtr &lhs, HasPtr &rhs)
{...}

void swap(int &lhs, int &rhs) { std::swap(lhs, rhs); }
void swap(char &lhs, char &rhs) { std::swap(lhs, rhs); }
// etc. for other std::swap overloads of common types

void swap(Foo &lhs, Foo &rhs)
{
    swap(lhs.h, rhs.h);
    // the other overloads of swap go out of scope here.
}

Регулярные правила перегрузки гарантируют, что первая своп - это тот, который вызывается. Это контрастирует с объявлением локальной переменной с именем "swap", которая будет скрывать первую перегрузку.

Ответ 5

Собственно, swap для HasPtr скрыт во время using std::swap во внутренней области, но в этом случае std::swap во внутренней области совпадает с swap для HasPtr во внешней области.