这是标准的C++代码吗?

4
以下这段简单的代码,可以在VC2008中编译通过,但是g++却无法通过编译:
#include <iostream>

class myclass
{
protected:
    void print() { std::cout << "myclass::print();"; }
};

struct access : private myclass
{
    static void access_print(myclass& object)
    {
        // g++ and Comeau reject this line but not VC++
        void (myclass::*function) () = &myclass::print;

        (object.*function)();
    }
};

int main()
{
    myclass object;
    access::access_print(object);
}

(/W4) 在 VC 中被开启,但是并没有发出任何警告。

然而,g++ 4.4.1 给了我一个错误:

correct.cpp: In static member function ‘static void access::access_print(myclass&)’:
correct.cpp:6: error: ‘void myclass::print()’ is protected

如果g++是正确的,我如何访问类的受保护成员?还有其他方法吗?
@Suroot你的意思是我不应该传递一个myclass类型的对象吗?实际上这并不重要,g++会给出相同的错误,但VC编译代码时没有任何警告。
#include <iostream>

class myclass
{
protected:
    void print() { std::cout << "myclass::print();"; }
};

struct access : private myclass
{
    static void access_print()
    {
        myclass object;
        void (myclass::*function) () = &myclass::print;

        (object.*function)();
    }
};

int main()
{
    access::access_print();
}

@AraK,请查看此问题的评论:https://dev59.com/iHI-5IYBdhLWcg3wsKr9 :) - Johannes Schaub - litb
@litb 直到现在我才看到你的评论 >_< 感谢你在评论中的澄清,你得到了我的赞同 :) - Khaled Alshaya
3个回答

13

以下内容正确。标准的相关部分已经引用了,但这里有一些值得一提的理由。

C++protected的语义是“该成员可以从该类或任何派生类的方法中访问,但仅当对象的动态类型被保证与*this的类型相同或派生自它时才能访问”。

后面的部分可能并不太明显,所以这里给出一个例子:

 class Base {
 protected:
     int X;
 };

class Derived : public Base {
   void Foo(Base* b, Derived* d) {
       this->X = 123; // okay - `this` is definitely either Derived
                      // or inherited from Derived

       d->X = 456;    // also okay, for the same reason

       b->X = 789;    // NOT OKAY; b could point to instance of some other class
                      // Derived2, which isn't inherited from Derived!
   }
};

这是有意为之的设计。其思想在于,Derived2 可以对其保护成员如何处理(不变量等)拥有自己的看法,而且应该严格限制在它和基类之间(以及它的派生类,但只有在它决定不通过将其设为 private 来隐藏该字段时才是如此)。跨层次访问与此模型相悖,因为这实际上意味着基类提前为整个层次结构做出决策;对于深层次结构之上的非常抽象的类来说,这可能是不可取的。
现在回到你具体的问题,这应该很明显了。一旦你获得一个成员函数指针,你可以使用任何匹配类型的接收器调用被该指针指向的函数。对于一个基类的受保护方法,这意味着,如果你可以获取一个指向该方法的指针,类型为基类(而不是你自己的类),那么你就可以调用它,并传递一个指向与你的类不同(或派生自它)类型的指针,从而违反了上面概述的受保护访问规则。因此,你不被允许这样做。

+1 感谢你的精彩解释,这个问题对我来说真是一个难题。 - Khaled Alshaya
1
看,这就是我第一次读到它时的想法,但既然你可以将&access::print的结果转换为void(myclass :: *)(),那么这是否意味着您可以随意将其应用于具有静态类型myclass的对象,而不考虑动态类型? - Todd Gardner
1
哇,想不到啊。你说得完全正确,托德 - 即使它强制你写 &Derived::Foo 才能让它工作(然后它就可以工作了!),表达式的实际类型仍然是指向 Base 的成员指针,然后你可以成功地通过基类指针使用它来访问受保护的成员。仔细阅读规范与这种解释一致,MSVC 2008 & 2010、g++ 3.4 和 Comeau 也是如此。我尝试在谷歌上搜索,但没有找到任何关于这个特定情况的讨论 - 我想我会在 comp.std.c++ 上开一个帖子... - Pavel Minaev
话虽如此,我认为该限制的目的可能就像我在这个答案中所描述的那样,但他们没有走得够远(忘记强制指针类型为“Derived”?)。 “protected”本身的意图在标准中已经非常清楚:“除了形成成员指针(5.3.1)时,访问必须通过派生类本身(或从该类派生的任何类)的指针,引用或对象进行(5.2.5)”,因为对于它们没有指针可以进行访问。 - Pavel Minaev
好的,这是一个已知的问题,并且之前由litb提出过;请参见https://dev59.com/b3VD5IYBdhLWcg3wI3-L#1065606和http://groups.google.com/group/comp.std.c++/browse_thread/thread/e4d67b76e73bdac1以及http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/ec22da1497adbf96。虽然当时有一些讨论,但没有任何进展,所以我再次提出了这个问题 - 希望它能引起一些反应(也许成为DR):http://groups.google.com/group/comp.std.c++/browse_thread/thread/37046682d4d570ed# - Pavel Minaev

6
我相信g++和comeau是正确的。保护成员的限定符必须是“access”或其派生类型,因此我认为以下代码是正确的:
void (myclass::*function) () = &access::print;

我相信这是因为11.5.1的原因:

...如果访问[ed。受保护的成员]是要形成指向成员的指针,则嵌套名称说明符应命名派生类(或从该类派生的任何类)。

将会编译。


刚在Comeau里试了一下,它可以工作。我从来没有真正理解为什么存在这个规则,但是有一段时间我被它咬了一口,只有经过相当多的抓狂之后才找到了它。 - Todd Gardner
哇...真的很令人印象深刻,我已经苦思冥想了好几天 :) - Khaled Alshaya
希望我的回答中的理由能够让你对它为什么会这样工作有一些了解。 - Pavel Minaev
引用的规则显然适用,但如何找到它并不明显。如果gcc能够说明它正在应用哪个规则,那就太好了。至于标准委员会可能在想什么,我曾经认为可能是虚函数表,但这似乎不太可能,因为指向基类的指针可以正确处理虚函数表。奇怪。 - Windows programmer
我觉得这个实现很有趣,因为你正在获取一个仅存在于结构体中的函数指针。而且,由于在这种情况下没有声明结构体,当调用函数(myclass::*function)()时,我很想知道它实际上引用了什么。 - Suroot
该函数存在于结构体及其基本类型中。myclass :: * function 引用基本类型的打印方法,因为赋值语句分配了该方法。如果标准允许此代码,则显然会发生什么。 - Windows programmer

2

由于对象作为参数传递,因此您无法直接访问私有/受保护的函数。

编辑: 将myclass更改为对象


我相信你的意思是“由于object作为参数被传递,…” - outis
请看修改后的代码片段。 - Khaled Alshaya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接