这个继承有什么问题?

4

我就是不明白。在VC++ 2008和G++ 4.3.2上都试了。

#include <map>


class A : public std::multimap<int, bool>
{
public:
    size_type erase(int k, bool v)
    {
        return erase(k); // <- this fails; had to change to __super::erase(k)
    }
};

int main()
{
    A a;
    a.erase(0, false);
    a.erase(0); // <- fails. can't find base class' function?!

    return 0;
}

不懂C++,但是你的erase方法是否忘记了任何"overload"指令? - vIceBerg
1
Greg Hewgill已经回答了你的问题,但是被注释掉的'return erase(k)'无法编译通过的原因是它需要改为'return std::multimap<int, bool>::erase(k)'。这种写法比__super更可取,因为__super是微软的扩展功能。 - nlativy
9个回答

26

当您在类中声明一个与超类名称相同但签名不同的函数时,名称解析规则指定编译器应在找到第一个匹配项后停止查找您尝试调用的函数。在通过名称找到函数之后,然后应用重载解析规则。

所以当您调用erase(0)时,编译器会找到您实现的erase(int, bool)函数,并决定参数不匹配。


好的解释。 有趣的是:相同的代码在C#中也能工作。 - Filip Frącz
那是因为C#是一种具有不同重载规则的不同语言。:)但是,我同意C#的重载规则更加直观。这个问题以前也曾让我困扰过。 - jalf

18

1: 当从C++标准库容器派生时,需要极度小心。虽然可以实现,但因为它们没有虚析构函数和其他类似的细节,通常不是正确的方法。

2: 这里的重载规则有些奇怪。编译器首先查找派生类,如果在派生类中找到了任何同名的重载函数,则停止在那里查找。仅当在派生类中未找到任何重载函数时,才会查找基类。

一个简单的解决方案是将所需的函数从基类引入到派生类的命名空间中:

class A : public std::multimap<int, bool>
{
public:
        using std::multimap<int, bool>::erase; // Any erase function found in the base class should be injected into the derived class namespace as well
        size_type erase(int k, bool v)
        {
                return erase(k);
        }
};

当然,你也可以在派生类中编写一个小的辅助函数,将调用重定向到基类函数。


9

6
首先,您不应该派生STL容器,因为没有STL容器定义虚析构函数。
其次,请查看Greg有关继承的回答。

在我看来,虚析构函数的问题被高估了。如果你从不使用其基类类型,那么这就不是一个问题。 - Zan Lynx
如果你从未使用它的基本类型,那么有什么意义呢?(如果你是为了访问受保护成员而派生而不是使用它作为成员,那么你做错了。) - Marcin

5

请考虑是否真的需要继承 std::map。在我写代码的所有时间里,这段时间比 STL 存在的时间还要长,我从来没有见过继承 std::container 是最佳解决方案的情况。

具体来说,请问你的类是一个 multimap 还是有一个 multimap。


3
其他人已经回答了如何解决语法问题以及为什么从标准类派生可能很危险,但也值得指出:
优先使用组合而不是继承。
我怀疑您的意思不是让 'A' 显式地与 multimap 具有“is-a”关系。Sutter / Alexandrescu 的 C ++编程规范 有一整章讨论此问题(#34),Google 指向许多良好的参考文献
似乎还有一个 SO 主题 关于这个话题。

1
对于那些将Effective C++作为C++编程参考的人,本问题在该书的第33条(避免隐藏继承的名称)中有所涉及。

1

我同意其他人的评论,你需要非常小心地继承STL类,并且几乎总是应该避免。

然而,这个问题可能会出现在一些其他基类中,从这些基类中继承是完全合理的。

我的问题是:为什么不给你的两个参数的函数取一个不同的名字呢?如果它接受不同的参数,那么它显然有稍微不同的含义?例如,erase_if_true或erase_and_delete或任何布尔值的含义。


这取决于你如何看待这个问题。由于所讨论的函数仅依赖于A基类的公共成员,因此可以认为A是不必要的。相反,我们应该遵循S. Meyer的建议,使用非成员非友元函数。这正是C#扩展方法的用途。 - Filip Frącz

0
为了以可移植的方式替换__super,在类的顶部定义一个typedef,如下所示:
typedef std::multimap<int, bool> parent;
public:
    size_type erase(int k, bool v)
    {
            return parent::erase(k);
    }

当然,它不需要是“parent”。它可以是您喜欢的任何名称,只要在整个项目中始终使用。


“我使用 _Parent 是因为在某些库代码中看到了这种写法。” - 是的!只有库代码可以/应该使用以下划线开头的名称!因此:不要在正常代码中这样做! - Konrad Rudolph
更具体地说,只有标准库可以使用以下划线开头的名称,而不是任何库。 (特别是全局命名空间中的下划线,双下划线或下划线后跟大写字母。所有这些在您的代码中都是非法的。) - jalf
在没有有效名称隐藏的C语言中,这可能是有意义的,但在私有的、类本地定义中,谁在乎呢?谢谢,我会在我喜欢的地方放下划线。 - Zan Lynx
我不认为需要typedef。如果你从一个类继承,记住这个类在子类中是什么应该不难。 - Max Lybbert
如果您正在编写一个模板类,但原始示例中的A类不是模板化的。 - Max Lybbert
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接