为什么 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

本文关键字:重载 派生 基类 方法 指令 C++11 为什么 using      更新时间:2023-10-16

下面的示例得到以下编译错误:

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++11中没有改变。

它之所以没有改变,是因为这种行为并非偶然。这种语言是这样设计的。它有优点也有缺点,但这并不是因为标准委员会的人忘记了它才发生的

不,这是没有办法的。这只是C++中成员查找的工作方式

据我所知,这在C++11中发生了变化,所以你不需要输入using语句。这不对吗?

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

还有别的办法吗?

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

只是想澄清一下情况:我无法想象这在C++中会发生变化。为了使其成为一个可行的更改,您必须加强类型安全性,使其不再与C兼容(例如,您几乎必须消除所有隐式转换)。

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

考虑一个明显的替代方案:编译器没有在那一点上停止搜索,而是继续搜索作用域,基本上创建了所有这些名称的重载集,然后选择最匹配的名称。

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

int i;
int main() { 
    long i;
    i = 1;
    std::cout << i;
    return 0;
}

在当前规则下,这一点的含义是明确无误的:i=1;将值1分配给main本地定义的i

根据修订后的规则,这将是一个悬而未决的问题——事实上,情况可能不应该如此。编译器将找到i的两个实例,并且由于1的类型为int,因此它可能应该与全局i匹配。当我们打印出i时,编译器会发现一个使用long的重载,因此它会打印出本地i(它仍然包含垃圾)。

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

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

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

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

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

这两种方法都可能奏效,但(至少)你需要相当多的限制,以防止它们导致近乎疯狂的混乱。例如,考虑像a =1; a = '2';这样的东西。现在,这些必须引用同一个变量——但对于这些规则中的第一个,它们不会。

对于一些特殊情况,您可能也可以消除这种特殊的奇怪之处——例如,使用当前规则查找变量名,而新的/单独的规则仅用于函数名。

总结:简单/显而易见的方法会创建一种几乎无法修复的语言。为防止这种情况发生而进行的修改是可能的,但代价是放弃与C和基本上所有现有C++代码的兼容性。后者在一种全新的语言中可能是可能的,但对于像C++这样已经建立得很好的语言来说(尤其是在很大程度上基于向后兼容性建立的语言上——再次使用C++)。