C++中的函数隐藏和使用声明

Posted

技术标签:

【中文标题】C++中的函数隐藏和使用声明【英文标题】:Function hiding and using-declaration in C++ 【发布时间】:2017-07-12 07:43:26 【问题描述】:

我的困惑来自“C++ Primer 5th edition”第 13.3 节,第 518 页。

非常细心的读者可能想知道为什么swap 中的using 声明没有隐藏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);

为什么HasPtrswap 没有隐藏,它似乎在外部范围内声明,而using std::swap 在内部范围内?谢谢。

【问题讨论】:

因为using对此没有影响 【参考方案1】:

因为using std::swap; 并不意味着“从今以后,每个'交换'都应该使用std::swap”,而是“将swap 的所有重载从std 带入当前范围”。

在这种情况下,效果与您在函数中写入using namespace std; 相同。

【讨论】:

我理解using 声明在std 中引入了swap,但如果它的工作方式类似于普通函数声明,它应该隐藏外部空间的名称,而不仅仅是引入名称std。顺便说一句,***.com/questions/32265938/… 说usingdeclaration 和指令是不同的,这让我很困惑。谢谢。 声明和指令不同的,但正如我写的,在这种情况下效果是一样的。 谢谢。如果f 是变量,那么它应该被隐藏,对吧?变量和函数的声明有不同的隐藏策略? 其实我刚试过,在你的例子中,外层空间的f会被隐藏。 @Sean 是的,你是对的。对不起。我不应该在发烧时这样做。【参考方案2】:

using-declaration using std::swap 不会隐藏您声明交换HasPtrFoo 的函数。它将名称 swapstd 命名空间带到声明性区域。这样,std::swap 就可以参与重载决议。

来自 C++11 标准:

7.3.3 using 声明

1 using-declaration 将名称引入到 using-declaration 出现的声明性区域中。

使用声明:

using typenameoptnested-name-specifier unqualified-id ;using :: unqualified-id ;

using-declaration 中指定的成员名称在 using-declaration 出现的声明区域中声明。 [ 注意: 只有指定的名字是这样声明的;在 using-declaration 中指定枚举名称不会在 using-declaration 的声明区域中声明其枚举数。 —结束说明 ] 如果一个 using-declaration 命名了一个构造函数(3.4.3.1),它隐式声明了一个类中的一组构造函数,其中 using-声明出现(12.9);否则,在 using-declaration 中指定的名称是其他地方声明的某个实体名称的同义词。

在你的情况下,你有:

void swap(Foo &lhs, Foo &rhs)

    using std::swap;
    swap(lhs.h, rhs.h);

using std::swap; 声明将来自 std 命名空间的名称 swap 引入声明区域,即 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);
    
    

【讨论】:

“使用声明不隐藏任何东西” - 它确实隐藏了在全局命名空间中声明的swap,以便在这种情况下进行简单的非限定查找。但是,ADL 仍然可以找到 swap,因为 HasPtr 的关联命名空间是全局命名空间。如果全局 swap 具有 int& 的参数,并且您使用 int 类型的参数调用它(没有关联的命名空间,因此 ADL 找不到任何东西),您会看到由using 声明(将调用 std 重载,即使全局重载更匹配)。 @bogdan,感谢您指出错误。我同意你评论中的一切。 @bogdan 我喜欢你的回答,但因为它是评论而不是答案,所以......我投票给你。我在 VS 2015 上试过,using-declaration 确实隐藏了这个功能,它是 HasPtr 的参数拯救了世界。 HasPtr 是在全局命名空间中定义的,因此搜索全局命名空间并找到 swapHasPtr “全局命名空间中的名称交换仍然可以在函数体中访问。”-- 为什么这是真的?我从 c++ 入门 (5th, 6.4.1) 中读到“如果我们在内部范围内声明一个名称,该名称将隐藏在外部范围内声明的该名称的使用。名称不会跨范围重载。" @ivy,这是真的,但您仍然可以将名称从外部范围带到内部范围以进行重载解析。见ideone.com/tTtUsG。【参考方案3】:

Effective C++ Third Edition by Scott Meyers(第 24 条)

当编译器看到对 swap 的调用时,他们会搜索正确的 swap 以 调用。 C++ 的名称查找规则确保这将找到任何 全局范围内或与类型在同一命名空间中的特定于 T 的交​​换 T.

在这种情况下,在第二个代码块中,编译器会查找 HasPtr 交换,如果找不到,则会使用 std 中的通用版本。

【讨论】:

【参考方案4】:

using 声明意味着考虑所有 std::swap 重载,就好像它们被定义在与函数相同的命名空间中一样。由于 using 声明出现在函数体中,这只是暂时的:在那个范围内。

效果与以下相同:

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】:

实际上,HasPtr 的swap 是隐藏的,而using std::swap 在内部范围内,但在这种情况下,内部范围内的std::swap 与HasPtr 在外部范围内的swap 相同。

【讨论】:

以上是关于C++中的函数隐藏和使用声明的主要内容,如果未能解决你的问题,请参考以下文章

C++中,一个类,究竟有多少隐藏的默认函数?

在 C++ 库中隐藏 C 头文件中的函数

如何隐藏 C++ 头文件中的函数

C++ this 指针,函数调用中的隐藏参数

C++ explicit关键字

C++:explicit关键字