为什么在 C++11 中仍然需要“using”指令来从基类中提出在派生类中重载的方法

Why is the "using" directive still needed in C++11 to bring forward methods from the base class that are overloaded in the derived class

提问人:Jaime 提问时间:7/13/2012 最后编辑:Nicol BolasJaime 更新时间:7/13/2012 访问量:554

问:

下面的示例出现以下编译错误:

test.cpp: In function ‘int main(int, char**)’:
test.cpp:26:8: error: no match for call to ‘(Derived) (p1&)’
test.cpp:14:8: note: candidate is:
test.cpp:16:10: note: void Derived::operator()(const p2&)
test.cpp:16:10: note:   no known conversion for argument 1 from ‘p1’ to ‘const p2&’

据我了解,这在 C++11 中发生了变化,因此您不需要放入 using 语句。这不对吗?还有其他方法可以解决这个问题吗?

示例(使用 --std=c++11 使用 gcc 4.7 编译):

#include <iostream>
#include <string>

using namespace std;

struct p1{};
struct p2{};

struct Base
{
    void operator()(const p1&) { cout << "p1" << endl; }
};

struct Derived : public Base
{
    void operator()(const p2&) { cout << "p2" << endl; }
    //Works if I include: using Base::operator();
};

int main(int argc, char** argv)
{
    p1 p;
    p2 pp;
    Derived d;

    d(p);
    d(pp);
}
C++ GCC C++11 重载 using 指令

评论

1赞 Philipp 7/13/2012
如果要隐藏基类函数,该怎么办?删除使用声明的需要将是一个重大更改。至少在一份较新的文件草案中,我看到使用声明没有任何变化。using
5赞 AnT stands with Russia 7/13/2012
在这种情况下,您从哪里获得不再需要的信息?using

答:

3赞 bames53 7/13/2012 #1

据我了解,这在 C++11 中发生了变化,因此您不需要放入 using 语句。这不对吗?

不可以,成员函数仍然可以隐藏在 C++11 中。

还有其他方法可以解决这个问题吗?

使用声明是预期的补救措施。

3赞 jalf 7/13/2012 #2

据我所知,不,这在 C++11 中没有改变。

而之所以没有改变,是因为这种行为不是偶然的。该语言在设计上是这样工作的。它有利有弊,但这并不是因为标准委员会的人忘记了它而发生的事情。

不,没有办法绕过它。这就是成员查找在 C++ 中的工作方式

评论

0赞 Jaime 7/13/2012
那好吧。我希望也许我可以使用某种修饰符来告诉编译器将它们向前推进,但我想不是:(。谢谢!
1赞 jalf 7/13/2012
有。这就是声明的作用。;)using
2赞 Jerry Coffin 7/13/2012 #3

只是为了澄清情况:我无法想象这在C++中发生变化。为了希望使它成为一个站得住脚的改变,你必须加强类型安全,使其不再与C兼容(例如,你几乎必须消除所有隐式转换)。

情况相当简单:现在,名称查找在找到的第一个范围停止,该范围至少包括正在使用的名称的一个实例。如果该名称的实例与尝试使用该名称的方式不匹配,则编译将失败。

考虑一个显而易见的替代方法:编译器不会在此时停止搜索,而是继续搜索范围,基本上创建了所有这些名称的重载集,然后选择最匹配的那个。

在这样的情况下,在外部范围内看似微不足道的更改可能会(完全)无意中改变某些代码的含义。考虑这样的事情,例如:

int i;

int main() { 
    long i;

    i = 1;

    std::cout << i;
    return 0;
}

根据当前规则,其含义是明确无误的:将值 1 分配给定义的局部值。i=1;imain

根据修订后的规则,这将是有待商榷的 - 事实上,情况可能不应该如此。编译器会找到 的两个实例,并且由于 has type ,它可能应该与全局匹配。当我们打印出来时,编译器会发现一个重载,它需要 ,所以它打印出本地的(它仍然包含垃圾)。i1intiilongi

请注意,这增加了另一个皱纹:将指的是本地,因为有一个可以与之配合的重载。因此,您将拥有控制所用变量的变量类型,而不是控制所使用的重载的变量。我不确定,但我想这会使解析变得更加困难(很可能是 NP 困难或 NP 完全问题)。cout << i;i

简言之,任何代码(有意或无意地)在内部作用域使用了几乎任何类型的隐式转换,这种看似无关的外部作用域的更改可能会突然完全改变该代码的含义——并且如上面的例子一样,在这个过程中很容易彻底地破坏它。

在上面的例子中,只有六行代码,很容易弄清楚发生了什么。然而,考虑一下,当你(例如)在标头中定义一个类,然后将该标头包含到其他文件中时会发生什么——编译器查看你包含标头的其他代码,找到一个更好的匹配项,突然间,你发誓的代码被彻底审查和测试了。

不过,有了标题,情况会(或至少可能)变得更糟。定义类,并将标头包含在两个不同的文件中。其中一个文件在外部范围内定义变量、函数等,而另一个则没有。现在,一个文件中使用名称 X 的代码指的是全局变量,而另一个文件中的代码指的是本地代码,因为全局变量恰好在该文件中不可见。这将完全破坏模块化,并使几乎所有代码完全损坏(或至少是可损坏的)。

当然,还有其他可能性可能没有那么糟糕。一种可能性是消除所有隐式转换,因此只会考虑完美的类型匹配。这将消除大多数明显的问题,但只是以完全破坏与 C 的兼容性为代价(更不用说可能让很多程序员非常不高兴了)。另一种可能性是像现在一样进行搜索,在找到匹配项的第一个作用域停止搜索,然后继续搜索外部作用域,当且仅当编译失败时,如果它在内部作用域使用该名称。

这两种方法都可以工作,但(至少)你需要相当多的限制来防止它们导致几乎疯狂的混乱。举个例子,考虑一下现在,这些必须引用相同的变量 - 但是对于这些规则中的第一个,它们不会。a =1; a = '\2';

在一些特殊情况下,您可能也可以消除这种特殊的奇怪现象 - 例如,使用当前规则查找变量名称,而仅使用新/单独的规则查找函数名称。

总结:这样做的简单/显而易见的方法将创造一种几乎无可挽回地被破坏的语言。防止这种情况的修改是可能的,但代价是放弃与 C 和几乎所有现有 C++ 代码的兼容性。后者在一种全新的语言中可能是可能的,但对于像C++这样已经很成熟的语言(特别是主要基于向后兼容性的建立 - 再次是C++)。

评论

0赞 David Rodríguez - dribeas 7/13/2012
虽然我不认为重载解析(这是 using 声明生效的地方)可以应用于变量,并且虽然我相信问题只涉及继承关系,但完全相同的推理适用于通过派生类和基类更改和全局命名空间,以及两个不同的重载在不同的上下文中接受并存在的函数。+1mainiintlong