using-declaration 无法正常工作

using-declaration doesn't works correctly

在下面的示例中,我试图通过在 class Elayer -

#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;

我做错了什么?

声明

E->showEveryDept();

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

在你的 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";

你想错了。

C++ 有使用Name Lookup, it's a well built concept that doesn't often come to our minds, but this happens everywhere a name 的概念。通过这样做:

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

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

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


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

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

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

但你只是明确地 introduce 名字 showEveryDept()Employee class 变成 Elayer;引入了private访问。这意味着,我们不能在 class member/static 函数之外直接访问与 ELayer 关联的名称(或调用该函数)。

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

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

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

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

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

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,在这种情况下,请求从它的基础class:

调用这样的函数
  • 失败:

    D->Elayer::showEveryDept();        // fails! its private in Elayer
    
  • 有效:

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

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

注意:将基类classB中的任何成员函数的名称B::func(例如)引入派生的class[=42] =],不会覆盖 B::func,它只是使 B::func 在派生的 class 的上下文中对重载解析可见。查看 this question and this

的答案

What am I doing wrong here?

你没有做错任何事。*

是否是预期结果错误:

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

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

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

  • 名称 - 不合格的标识符。
  • 声明区域 - class 在其中声明了名称。
  • 访问权限 - 该区域内名称的权限。

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

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

这是您代码中名称和区域的可访问性 table:

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

ELayerusing Employee::showEveryDept;的效果是showEveryDeptELayer内的访问private.


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

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

访问控制还考虑到:

  • 调用上下文与找到名称的声明区域之间的关系。

例如,在 ELayer 的上下文中查找 showEveryDept 将找到具有访问权限 private 的组合 ELayer::showEveryDept

但是在 Employee 的上下文中查找相同的名称会找到具有访问权限 public.

的组合 Employee::showEveryDept

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

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

`E->showEveryDept`

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

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


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

virtual dispatch 处于活动状态时,调用函数会将调用重定向到该函数的最终覆盖程序。

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

Virtual dispatch 仅在虚函数被unqualified-id调用时激活,这意味着在函数命名前不带Bla::

因此,在您的代码中,E->showEveryDept 确实激活了虚拟分派。如上所述访问检查通过,然后 virtual dispatch 调用最终的 overrider,它恰好是本例中 Employee 中定义的主体。

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