【问题标题】:Dot or arrow operator vs. scope resolution operator for accessing base subobject用于访问基础子对象的点或箭头运算符与范围解析运算符
【发布时间】:2013-12-12 09:13:51
【问题描述】:

C++

给定一个基类Base和一个派生类DerivedDerived的构造函数首先构造的是Base子对象。由于它被称为子对象,我假设它可以像任何其他成员对象一样通过在Derived 对象上使用点运算符从客户端代码中访问。我还假设它可以通过this->BaseDerived 的实现代码中访问。完全由已初始化的对象名称后跟分号组成的语句应该可以编译,但也没有效果。按照这个逻辑,给定一个 Derived 对象 myderived,我尝试了:在客户端代码中使用 myderived.Base;,在 Derived 的实现中使用 this->Base;,这两个语句都无法编译。

为什么?我知道Base 本身是Base 类的名称,而不是Base 对象的名称。但我认为Basemyderived.(客户端代码)或this->(实现代码)前缀限定是指基本子对象,因为Base,没有任何前缀限定,是Base 子对象的引用方式在Derived 的构造函数初始化器中。请参阅下面的代码,该代码(除了注释掉的代码)适用于 VC12 和 g++ 4.8。 Derived 扩展了BaseDerived 的定义声明了一个Base 数据成员membase,所以我的Derived 对象应该包含两个Base 对象。假设成功编译不是任何编译器标准不符合的结果,控制台输出(在 cmets 中)显示两个不同 Base 对象的 int 成员 n 的不同值,这意味着在Derived 的ctor 初始化器中,Base 指的是继承的Base 子对象,而membase 指的是声明的数据成员对象。在 Derived 的 ctor 初始化器中,Base 专门引用继承的子对象,而不仅仅是任何 Base 对象或 Base 类。

#include <iostream>

struct Base {
    Base(int par) : n(par) {}
    void foo() { std::cout << "Base: " << n << std::endl; }
    int n;
};

struct Derived : Base {
    Derived() : Base(2), membase(3) {}
    Base membase;
    void foo() { std::cout << "Derived: " << n << std::endl; }

    // void g() { this->Base; } // Error in VC12 & g++ 4.8
    // ^ VC12: error C2273: 'function-style cast' : illegal as
    // right side of '->' operator
};

int main() {
    Derived myderived;

    // myderived.Base; //Error in VC12 & g++ 4.8
    // ^ VC12: error C2274: 'function-style cast' : illegal as
    // right side of '.' operator

    myderived.foo();           // OUTPUT: "Derived: 2"
    myderived.Base::foo();     // OUTPUT: "Base: 2"
    myderived.membase.foo();   // OUTPUT: "Base: 3"
}
  1. 同样,myderived.Base;this-&gt;Base; 不应该唯一引用继承的 Base 子对象并编译吗?

  2. myderived.Basethis-&gt;Base 中的 Base 是否指代 Base 子对象或 Base 类或任何东西?

  3. 一般来说,继承的基子对象是否被视为派生类的数据成员?

  4. Derived的角度来看,Base是否只引用Derived的构造函数初始化器上下文内的继承子对象,而只引用Derived之外的Base类ctor 初始化器?

  5. 如何通过Derived对象访问继承的Base子对象,如Derived的“Derived对象的继承Base子对象”实现代码和客户端代码?

  6. myderived.Base::foo()中使用范围解析运算符,其中foo()Base的一个方法,在VC12和g++ 4.8中编译。这是否意味着Basemyderived 的数据成员,因为它由myderived 和点运算符限定?如果是这样,那么 BaseBase 类还是 Base 子对象?

  7. myderived.Base.foo() 无法编译。对象成员的 AFAIK 访问在客户端代码中由对象名称和点运算符限定。由范围解析运算符而不是对象名称和点运算符限定的两种事物是(a)对属于命名空间的任何事物的外部访问和(b)静态数据成员的名称和成员函数的名称在其类定义之外定义的定义,在这种情况下,:: 之前的 Base 指的是 Base 类,而不是任何 Base 实例。这是否意味着myderived.Base 中的Base 是一个命名空间或引用该类?

  8. 如果是,那么它是命名空间还是引用类取决于它是否附加了::,后跟Base 的成员?

  9. 如果#7 的答案是肯定的,那为什么?这似乎与以下逻辑不协调:命名空间对一个变量的封装本身并不使命名空间能够封装或构造该变量类型的其他实例。命名空间只拥有该类型的一个实例——它包含的变量。作为类的一部分的成员也是如此,例如静态数据成员。该类只拥有该类型的一个实例——它包含的静态数据成员。相反,一个类的同名非静态数据成员与该类的实例一样多。

  10. 给定方法 h()BaseDerived 对象 myderived, myderived.Base::h(); 在 VC12 和 g++ 4.8 中编译。此外,g++ 4.8 can 在该语句中采用任意数量的额外Base::s,例如myderived.Base::Base::h();。这样的声明似乎暗示BaseBase 的成员。但是 VC12 给出了error C3083: '{ctor}': the symbol to the left of a '::' must be a type。但是给定Base 对象mybase,VC12 编译mybase.Base::h(); 就好了,这也意味着VC12 可以将一个类视为其自身的成员。但这与其无法编译先前的声明相矛盾。此外,VC12 无法编译具有任意数量额外 Base::s(例如 mybase.Base::Base::h())的任何版本的 mybase.Base::h();,但 g++ 可以。如果有的话,哪个编译器是正确的?

  11. 无论如何,这是否意味着命名空间或类可以包含自身?给定一个全局 int 变量 x,语句 ::::x;(带有两个范围解析运算符)在任一编译器中都不会编译,所以我假设全局范围不包含全局范围。

【问题讨论】:

  • 我不“拖钓”任何人的问题,尽管我确实拖钓了一些人的答案,因为在这方面有一些聪明的头脑网站,你永远不能停止学习。如果你发布了它,你这样做的目的不是为了做你现在说没有人(包括你自己)要求的事情吗?即,阅读它
  • @WhozCraig 嗯,这很长,但有一个中心论点。答案和问题一样流动。
  • @Potatoswatter 我同意,它实际上写得非常好,并且有条不紊地呈现,几乎可以作为分步分析和教程阅读。考虑到(显然)没有要求您这样做,我建议您解决每一点。
  • 那么,我可以收回收到的仅有的两个赞成票之一吗?如果我有能力和深入的知识来回答你的长篇大论,我会的。我发现它.. 幽默.. 长度和细节最终是名称查找规则和法规摘要的泛音,如果这是我被指责的,我只能接受。这让我想起了大学时代,当时教授们会说“我只有一​​个问题……而且它有 27 个子部分”。久违了。相反,我会简单地接受这种令人无法接受的情况,然后简单地说晚安。

标签: c++ inheritance namespaces scope-resolution dot-operator


【解决方案1】:
  1. 不,您可以有一个名为Base 的成员,它与Base 子对象是分开的。 :: 标点符号将名称解析限制为忽略成员对象名称。
  2. 参见#1。通常答案是否定的,因为如果你故意让一个成员别名为基地,你会发疯的。但是,模板可能会发生这种情况,其中类可能不知道其基类的名称。
  3. 没有。成员子对象和基础子对象都是子对象,但它们的访问方式不同。
  4. 总是引用类,名称Base本身继承自Base。如果您有一个疯狂的成员别名,那么您需要使用对 Base 的其他引用,例如命名空间限定的 id。
  5. static_cast&lt; Base &amp; &gt;( derived_obj )
  6. 不,:: 的优先级高于.,因此在myderived 中查找Base::foo 成员,然后应用函数调用运算符。但是,(Base::foo) 周围不允许使用括号,因为 :: 不是生成子表达式的运算符;这就是为什么我更喜欢称它为标点符号。
  7. 参见#6。 myderived.Base 本身并不是任何东西,因为在. 之前带有:: 的基组。
  8. 没错。请注意,类不是命名空间;它们是不同的东西,碰巧使用相同的范围符号。
  9. 这似乎是用可能适用于另一种语言的术语来解释 C++。例如,在 Java 中,类是一种具有自己数据成员的对象。在 C++ 中,static 类成员和命名空间成员是完全独立的对象,可以在任何地方定义。
  10. Base::Base::Base:: 之所以有效,是因为一个类的名称被注入到自身中,就好像它是 typedef 的成员一样。 VC 可能会出错并将其解释为对构造函数的引用。根据规范,特殊的typedef(称为injected-class-name)是指特殊情况下的构造函数,但在作用域运算符之前不是这样的情况。
  11. 每个类都包含一个隐含的typedef。同样,命名空间和类是完全不同的东西。

    前缀:: 本身并不是全局命名空间的名称,而只是语法中的一种特殊情况,以弥补其名称的缺失。同样,无论好坏,您都不能声明

    namespace global = :: ; // error: :: alone does not name anything.
    

【讨论】:

  • +1 一流的深入研究。实际上,我很惊讶这个网站上的一些答案关于名称查找规则的弱点。 ADL 当然会被打死,但似乎非 ADL 问题由于某些奇怪的原因而被搁置了(或者我只是没有很好地搜索它们,总是有可能的)。
  • @WhozCraig 这个站点很好地涵盖了 ADL 的表面,但我不知道有任何资源(除了标准和 ISO 缺陷报告注册表)来学习名称查找的细微差别 —还有很多。
  • 这是深层次的事实,这是肯定的。这似乎也是一个有趣的症结,编译器供应商,如果他们要搞砸什么,喜欢在那里做。有时我认为标准本身并没有带来足够的清晰度作为其根本原因,但要说得一清二楚确实是一件困难的事情;比大多数人想象的要难。
  • 你真的可以拥有一个名为BaseBase 成员吗?当然,这就是构造函数的名称......不过,您可能有一个名为 BaseDerived 成员。 (哦,还有 +1)
  • @rici C++ 中的构造函数没有名称。
猜你喜欢
  • 2011-06-11
  • 2012-04-19
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
  • 1970-01-01
  • 2011-02-23
  • 2015-11-15
  • 2015-12-18
相关资源
最近更新 更多