在派生类中访问成员变量的地址。当成员具有不同的访问说明符时,行为会发生变化

时间:2013-01-18 13:53:21

标签: c++ visual-c++ c++11 public visual-c++-2010

我有一个基类A和派生类B

B类派生自A作为公共

如果 A是a类是成员变量

,我想访问成员变量的地址

当我使用受保护和公共访问说明符时,我正在观察不同的行为。

当A类成员a 受保护时,我得到:

cout<<&A::a << endl;给我一个编译错误..

但是cout<<&(A::a)<<endl;是有效的并且给我正确的结果。

当A级成员a 公开时,我得到的是:

为什么会出现这种情况?

以下是完整代码:

#include <iostream>
using namespace std;

class A
{
    protected:
    int a;
    void fun() 
    {
    cout<<"A's fun"<<endl;
    }

public:
    A(int Aarg):a(Aarg)
    {
        cout << "A's construction done" <<endl;
    }
};


class B: public A
{
protected:
int b;
public:
void fun()
{
cout << "B's fun"<<endl;
}

B(int As, int Bs):A(As), b(Bs)
{
std::cout<<"B's Construction Done" <<std::endl;
}

void show()
{
A::fun();
//cout << a << endl;
cout<<&A::a << endl; //compilation error
}
};

int main()
{
    B(10,20).show();
    return 0;
}

现在我可以观察到一种未定义的行为:

如果我在A类中将我的成员变量设为公共,那么就不会出现任何编译错误,但是输出结果为1我不知道为什么..

class A{
public:
int a
....
....

输出:

A的施工完成

B的建设完成

一个有趣的

0027F91C

1(为什么1)并且当我尝试访问受保护的成员时没有出现错误?

3 个答案:

答案 0 :(得分:4)

你在语法中遇到了一个小怪癖(并不是说C ++中很少有......)。访问成员变量的默认方式是直接使用名称或通过this->。也就是说,show函数的简单拼写是:

void B::show() {
   std::cout << a << std::endl;     // alternatively this->a
   std::cout << &a << std::endl;    //               &(this->a)
}

其语法简单一致。现在,该语言允许您在访问成员时放入额外的限定符来访问基类的成员:

std::cout << A::a << std::endl;     // Extra qualification

这实际上相当于this->A::a,并且额外资格的主要用途是消除歧义(如果两个基地有一个成员a,请选择A中的一个)和虚函数禁用动态调度的情况。

现在,您可以使用指针执行相同操作,如&this->A::a中所示,它将获取当前对象的子对象a中成员A的地址。这种情况下的问题是您不能删除this->限定符,因为保留语法&A::a以获取指向成员的指针,尽管通过添加一组额外的括号,如{{1}解析器不能再将其解释为获取指向成员的指针,而是将&(A::a)表示的对象的地址解释为,如前所述,它是基类中的成员。

答案 1 :(得分:3)

简短回答:没有涉及未定义的行为。你看到的行为是:

  • 表达式&A::a是尝试获取指向指向A类成员a的成员的指针。如果a在A中受保护,则此表达式仅传递A类(或A的朋友)中的访问检查。在从A派生的B类中,只能通过表达式&B::a获得指向成员的相同指针(请注意,此表达式的类型仍为int A::*)。所以:
    • 如果在{A}中保护A::a,则派生类B的成员函数中不允许使用表达式&A::a。这是您的编译器错误。
    • 如果A::a在A中公开,则此表达式有效,生成指向memeber的指针。
  • 将指向成员的指针流式传输到ostream,例如使用cout << &A::a将打印1。这是调用ostream::operator << (bool)的结果。你可以使用boolalpha操纵器来确定这确实是选择的重载:cout << boolalpha << &A::a将打印true
  • 如果使用修改后的表达式&amp;(A :: a)或简单地使用&amp; a,则不会形成指向成员的指针。这里采用当前对象的成员a的地址(即与&(this->a)相同),这是指向int的常规指针。对*this基类子对象的受保护成员的此访问权限是有效的,因此即使在A中受保护,也可以使用该变体。

更长的解释:

标准说(5.3.1 / 3):

  

一元&amp;的结果operator是指向其操作数的指针。该   操作数应为左值或合格ID。如果操作数是a   qualified-id命名某个类C的非静态成员m,类型为T,   结果有类型“指向T类C类成员的指针”,是一个   prvalue指定C :: m。 [...]

因此表达式&A::a尝试获取指向成员a的成员指针。

在下一段(5.3.1 / 4)中,详细说明只有&amp; X :: m语法产生指向成员的指针 - 既不是&(X::m),也不是&m或者是{ {1}}做:

  

指向成员的指针仅在显式&amp;使用和它的   操作数是一个未括在括号中的限定id。

但是如果允许访问,这样的表达式才有效。如果受保护的成员(11.4 / 1)适用:

  

除了前面第11章中描述的那些之外的附加访问检查   在非静态数据成员或非静态成员函数时应用   是其命名类的受保护成员(11.2)如上所述   之前,因为引用而授予对受保护成员的访问权限   发生在朋友或某些C类的成员中。如果要形成   指向成员(5.3.1)的指针,嵌套名称说明符应表示C   或来自C的类[...]

在您的情况下,将授予对受保护成员的访问权限,因为对A的引用发生在从B派生的B类成员中。当表达式尝试形成指向成员的指针时,嵌套 - name-specifier (最后的“:: a”之前的部分)必须表示B.因此,最简单的允许形式是X::m。表格&B::a仅允许在A类成员或朋友中使用。

指向成员的指针没有格式化的输出运算符(既不是istream成员也不是自由运算符函数),因此编译器将查看可以使用标准转换(序列)调用的重载。 4.12 / 1中描述了从指针到成员的唯一标准转换:

  

指向成员类型的指针的prvalue可以转换为a   bool类型的prvalue。转换[...]空成员指针值   为假;任何其他值都转换为true。 [...]

无需额外转换即可使用此转化来调用&A::a。其他重载需要更长的转换序列,因此过载是最佳匹配。

由于basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n)获取某个成员的地址,因此它不是空成员指针值。因此它将转换为&A::a,其打印为“1”(noboolalpha)或“true”(boolalpha)。

最后,表达式true在B的成员中有效,即使a在上述规则中受A保护,此表达式也不形成指向成员的指针,因此上面引用的特殊访问规则确实如此不适用。对于这种情况,11.4 / 1继续:

  

所有其他访问都涉及(可能是隐式的)对象表达式   (5.2.5)。在这种情况下,对象表达式的类应为C.   或者来自C的类。

此处对象展示是隐式&(A::a),即(*this)表示与A::a相同。 (*this).A::a的类型显然与发生访问的类(B)相同,因此允许访问。 [注意:B中不允许(*this)。]

int x = A(42).a中的&(A::a)表示与B::show()相同,这是指向int的普通指针。

答案 2 :(得分:-5)

这是一个语法问题。 &amp;意味着你要求在右边的元素地址。

如果你写:

&A::a

就像你写的那样

(&A)::a

这意味着你要求从'&amp; A'访问'a',这是不对的。

&amp;不仅适用于变量,也可以在函数上使用,请参阅:http://www.cprogramming.com/tutorial/function-pointers.html