草庐IT

c++ - 为什么覆盖虚函数时不考虑访问限定符?

coder 2024-02-20 原文

以下代码打印“I'm B!”。这有点奇怪,因为 B::foo() 是私有(private)的。关于A* ptr,我们可以说它的静态类型是A(foo是public),它的动态类型是B(foo 是私有(private)的)。所以我可以通过指向 A 的指针调用 foo。但是这样我就可以访问 B 中的私有(private)函数。可以算作封装违规吗?

由于访问限定符不是类方法签名的一部分,因此会导致这种奇怪的情况。为什么在覆盖虚函数时不考虑 C++ 中的访问限定符?我可以禁止这种情况吗?这个决定背后的设计原则是什么?

Live example .

#include <iostream>

class A
{
public:
    virtual void foo()
    {
        std::cout << "I'm A!\n";
    };
};

class B: public A
{
private:
    void foo() override
    {
        std::cout << "I'm B!\n";
    };
};

int main()
{
    A* ptr;
    B b;

    ptr = &b;

    ptr->foo();
}

最佳答案

你有很多问题,所以我会尽量一一回答。

为什么重写虚函数时不考虑 C++ 中的访问限定符?

因为在所有重载决议之后,编译器都会考虑访问限定符。 这种行为由标准规定。

例如,参见 on cppreference :

Member access does not affect visibility: names of private and privately-inherited members are visible and considered by overload resolution, implicit conversions to inaccessible base classes are still considered, etc. Member access check is the last step after any given language construct is interpreted. The intent of this rule is that replacing any private with public never alters the behavior of the program.

下一段描述了您的示例所展示的行为:

Access rules for the names of virtual functions are checked at the call point using the type of the expression used to denote the object for which the member function is called. The access of the final overrider is ignored.

另请参阅 this answer 中列出的操作顺序.

我可以禁止这种情况吗?

没有。

而且我认为您永远无法这样做,因为这种行为并不违法。

这个决定背后的设计原则是什么?

澄清一下:这里的“决定”是指编译器在重载解析后检查访问限定符的规定。 简短的回答:防止在更改代码时出现意外。

有关更多详细信息,我们假设您正在开发一些如下所示的 CoolClass

class CoolClass {
public:
  void doCoolStuff(int coolId); // your class interface
private:
  void doCoolStuff(double coolValue); // auxiliary method used by the public one
};

假设编译器可以根据公共(public)/私有(private)说明符进行重载解析。然后下面的代码将成功编译:

CoolClass cc;
cc.doCoolStuff(3.14); // invokes CoolClass::doCoolStuff(int)
  // yes, this would raise the warning, but it can be ignored or suppressed 

然后在某些时候您发现您的私有(private)成员函数实际上对类客户端有用并将其移至“公共(public)”区域。这会自动更改预先存在的客户端代码的行为,因为现在它调用CoolClass::doCoolStuff(double)

因此,应用访问限定符的规则是以不允许允许这种情况的方式编写的,因此您会在一开始就遇到“模棱两可的调用”编译器错误。出于同样的原因,虚函数也不是特例(参见 this answer)。

可以算作封装违规吗?

不是真的。 通过将指向您的类的指针转换为指向其基类的指针,您实际上是在说:“在此我想使用这个对象 B,就好像它是一个对象 A”——这是完全合法的,因为继承意味着“按原样” "关系。

所以问题是,您的示例是否可以视为违反了基类规定的契约?好像是的,可以。

查看 this question 的答案其他解释。

附言

不要误会我的意思,所有这些并不意味着您不应该使用私有(private)虚函数。相反,它通常被认为是一种好的做法,请参阅 this thread .但是它们应该从最基类是私有(private)的。所以,底线是,你不应该使用私有(private)虚函数来破坏公共(public)契约。

附言...除非您故意要强制客户端通过指向接口(interface)/基类的指针使用您的类。但是有更好的方法,我相信对这些的讨论超出了这个问题的范围。

关于c++ - 为什么覆盖虚函数时不考虑访问限定符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45671799/

有关c++ - 为什么覆盖虚函数时不考虑访问限定符?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  6. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  7. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  8. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  9. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  10. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

随机推荐