【问题标题】:using-declaration doesn't works correctlyusing-declaration 无法正常工作
【发布时间】:2017-01-25 09:05:00
【问题描述】:

在下面的示例中,我试图通过将 Elayer 设为私有类中的最后一个子类 Designer 来隐藏 using Employee::showEveryDept -

#include <iostream>

class Employee {
private:
    char name[5] = "abcd";
    void allDept() { std::cout << "Woo"; }

public:
    void tellName() { std::cout << name << "\n"; }
    virtual void showEveryDept()
    {
        std::cout << "Employee can see every dept\n";
        allDept();
    }
};

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

class Designer : public ELayer {
private:
    char color = 'r';

public:
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; }
};

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

但它仍在编译,输出是 -

Employee can see every dept
Woo
Designer can see own dept

但我已明确将其设为私有,请参阅 - private: using Employee::showEveryDept;

我在这里做错了什么?

【问题讨论】:

  • @πάνταῥεῖ 请告诉一个使用 gui 的优秀的基于 linux 的编译器。我目前对直接使用 gdb 并不太习惯购买 windows。
  • @hg_git 如果您要我推荐 IDE,我建议您使用 Eclipse CDT。
  • @hg_git 普通命令行 gdb 然后。即使在我蹩脚的笔记本电脑上,我也可以使用 Eclipse。
  • @πάνταῥεῖ 调试器无助于找出代码未生成预期编译错误的原因

标签: c++ c++11 inheritance polymorphism using-declaration


【解决方案1】:

类成员的名称具有以下属性:

  • 名称 - 一个不合格的标识符。
  • 声明性区域 - 在哪个类中声明了名称。
  • 访问 - 该区域内名称的权限。

这适用于名称本身——不适用于名称所指的任何变量或函数。可以有相同的函数或变量以相同的名称命名但在不同的声明区域中。

当一个类被继承时,派生类的声明区域包括来自基类的所有名称;但是可以根据继承的类型更改访问权限:虽然只能将成员声明为publicprotectedprivate,但在继承之后您可能会得到一个没有访问权限的成员.

下面是代码中名称和区域的可访问性表:

请注意tellName 在所有三个类中是如何公开的,尽管它没有在Designer 中重新声明。因此,ELayerusing Employee::tellName; 是多余的,因为无论如何tellNameELayer 中本来就是public

ELayerusing Employee::showEveryDept;的效果是showEveryDept的访问ELayerprivate


名称查找 是通过调用名称来解析找到哪个名称-区域组合的过程。此调用的上下文包括:

  • 调用站点,即使用名称的范围
  • 调用中任何明确列出的范围(例如Foo::name
  • 表示正在访问其成员的对象的表达式(例如(*E)

访问控制还考虑到:

  • 调用上下文和名称所在的声明区域之间的关系。

例如,在ELayer 的上下文中查找showEveryDept 将找到ELayer::showEveryDept 与访问private 的组合。

但是在 Employee 的上下文中查找相同的名称会发现组合 Employee::showEveryDept 可以访问 public

无论这两个组合是否引用相同的函数,此行为都是相同的。

在不复制有关调用上下文如何转换为搜索声明性区域的完整规则列表的情况下,用法:

`E->showEveryDept`

*E静态类型区域中查找名称,即Employee。它不使用动态类型,因为名称查找是在编译时解析的。没有运行时访问错误——访问是编译时属性。

访问检查的最后一步是将publicEmployee 与调用站点进行比较,即main()。规则是public 授予对所有呼叫站点的访问权限,因此访问检查通过。


virtual-ness 不依赖于名称的属性,也不依赖于查找名称的范围。与 access 不同,虚拟是函数的属性,而不是任何名称-区域组合的属性。

虚拟调度处于活动状态时,调用一个函数会将调用重定向到该函数的最终覆盖器。

从函数实现的角度考虑这一点很重要,而不是函数的名称。虚拟调度和访问控制是两个完全独立的操作。

只有当一个虚函数被一个 unqualified-id 调用时,虚拟调度才有效,这意味着通过在前面不带Bla:: 来命名函数。

因此,在您的代码中,E-&gt;showEveryDept 确实激活了虚拟调度。访问检查如前所述通过,然后虚拟分派调用最终覆盖器,在此示例中恰好是 Employee 中定义的主体。

在您的实际示例中,virtual 没有实际意义,因为该函数未被覆盖。但即使您在 ELayer 中将 showEveryDept 重写为私有函数(而不是 using 声明),它仍然会调用该函数体。

【讨论】:

  • 你一定是一名专业教师,确切地知道我在理解这个问题时缺少什么:D
  • @hg_git heh,谢谢你的奖励......我不确定你错过了什么,但我试图击中所有的基地
【解决方案2】:

你想错了。

C++ 有Name Lookup 的概念,这是一个构建良好的概念,我们并不经常想到,但在使用name 的任何地方都会出现这种情况。通过这样做:

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

E-&gt;showEveryDept() 行对属于E 类的成员(在本例中为成员函数)执行非限定名称查找。因为是accessible name,所以程序是合法的。


我们也知道Designer 派生自ELayer,其中showEveryDept() 声明为private,就像您在此处所做的那样:

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

但是您所做的只是将introduce 的名称showEveryDept()Employee 类显式转换为Elayer;引入private 访问。这意味着,我们不能在类成员/静态函数之外直接访问与ELayer 关联的名称(或调用该函数)。

ELayer* e = new Designer();
e->showEveryDept();    //Error, the name showEveryDept is private within ELayer

但是,由于showEveryDept()Elayer 的基类中具有public 访问权限,Employer;仍然可以使用qualified name lookup访问它

ELayer* e = new Designer();
e->Employer::showEveryDept();    //Ok, qualified name lookup, showEveryDept is public

Elayer 中可在Designer 中访问的名称将由其access specification 指定。如您所见,名称showEveryDept() 是私有的,因此Designer 甚至不能使用这样的名称。

对于您当前的类层次结构和定义,这意味着:

Employee* E = new Designer();
ELayer*   L = new Designer();
Designer* D = new Designer();

-- 对于unqualified lookup:

  • 这行得通

    E->showEveryDept();                // works!
    
  • 这失败了:

    L->showEveryDept();                // fails; its private in Elayer
    
  • 这也失败了:

    D->showEveryDept();                // fails; its inaccessible in Designer
    

-- 对于qualified lookup,在这种情况下,请求从其基类调用这样的函数:

  • 这失败了:

    D->Elayer::showEveryDept();        // fails! its private in Elayer
    
  • 这行得通:

    D->Employee::showEveryDept();      // works! its accessible in Employee
    
  • 这也有效:

    L->Employee::showEveryDept();      // works! its accessible in Employee
    

注意:将任何成员函数的名称,B::func(例如)从基类B 引入派生类D,不会覆盖B::func,它只会使B::func在派生类的上下文中对重载决议可见。查看this questionthis 的答案

【讨论】:

  • 我的目标是让它直接被Employee类对象调用,但如果它被派生自ELayer的任何人调用,则会失败
  • 这是一个非常好的答案,也是我接受其他答案的唯一原因,因为这对我来说更清楚。不过你的回答确实对我有很大帮助:)
【解决方案3】:

我在这里做错了什么?

你没有做错任何事。*

预期的结果是否错误:

在 Employee 类型上检查虚函数的访问说明符,而不是在 Designer 上检查,如您所料 => link

(*) 除了将访问规则更改为层次结构上的虚拟方法这一事实在我看来是糟糕的设计 => check this

【讨论】:

    【解决方案4】:

    在您的 main() 函数中,明确说明您的类型并这样称呼它 -

    int main()
    {
        Employee* E = new Designer;
        E->showEveryDept(); // will work
    
        Designer* D = dynamic_cast<Designer*>(E);
        D->showOwnDept();
        D->showEveryDept();   // <-- Not legal now
    }
    

    它会产生错误 -

    prog.cc: In function 'int main()':
    prog.cc:28:22: error: 'virtual void Employee::showEveryDept()' is inaccessible within this context
         D->showEveryDept();
                          ^
    prog.cc:8:26: note: declared here
                 virtual void showEveryDept(){std::cout<< "Employee can see every dept\n";
    

    【讨论】:

      【解决方案5】:

      声明

      E->showEveryDept();
      

      *E 的编译时已知类型访问showEveryDept。这是Employee,可以访问此成员。

      【讨论】:

      • 您介意纠正我吗? Employee* E = new Designer; 定义了 Employee 类型的指针,但指向 Designer 类型的对象,对吧?所以E-&gt;showEveryDept() 实际上是在Designer 对象上调用showEveryDept() 方法,或者不是?因为那样它隐藏得很好!
      • 是的,虚拟调用解析到最派生的类。但是规则是这样的,因为在一般情况下,大多数派生类在编译时无法知道,即使使用全局分析也是如此。例如。它可以取决于用户输入。
      • 不确定您是否认为private 虚拟成员函数不能被虚拟调用。他们能。许多人建议创建虚拟成员函数private,尽管出于某种无法解释的原因(我不记得与 Marshall 讨论过这个问题),这不是旧常见问题解答的建议,因此也可能不是 ISO CPP 常见问题解答。跨度>
      • @AbhinavGauniyal:谢谢!摘要:ISO CPP 常见问题解答确实推荐了私有虚拟,并解释了为什么旧的常见问题解答不推荐(新手混淆的风险)。
      猜你喜欢
      • 2016-12-01
      • 2012-05-17
      • 1970-01-01
      • 2016-09-01
      • 2012-07-11
      • 2018-04-08
      • 2017-04-20
      • 2018-10-02
      • 2016-09-04
      相关资源
      最近更新 更多