【问题标题】:Is this a standard C++ code?这是标准的 C++ 代码吗?
【发布时间】:2010-12-19 17:56:27
【问题描述】:

下面一段简单的代码,用 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();
}

【问题讨论】:

标签: c++ standards standards-compliance


【解决方案1】:

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

编辑: 将 myclass 更改为对象

【讨论】:

  • 我相信你的意思是“由于object 被作为参数传递,......”
  • 请看修改后的sn-p。
【解决方案2】:

我相信 g++ 和 comeau 是正确的。受保护成员的说明符必须是“访问”类型或派生的,所以我相信代码:

void (myclass::*function) () = &access::print;

会编译。

我相信这是因为 11.5.1:

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

【讨论】:

  • 刚在 Comeau 试过;有用。我从来没有真正理解为什么会有这个规则,但我前一阵子被它所吸引,直到拉了相当多的头发后才发现它。
  • 哇...真是令人印象深刻,我这几天一直在摸不着头脑:)
  • 希望我的回答中的基本原理能够提供一些关于它为什么会这样工作的见解。
  • 引用的规则显然适用,但如何找到它并不明显。如果 gcc 能说出它应用了什么规则,那就太好了。至于标准委员会可能在想什么,有一分钟我认为它可能是 vtbl,但这似乎不太可能,因为指向基类的指针可以正确处理 vtbl。很奇怪。
  • 我发现这个实现很有趣,因为您正在获取指向仅存在于结构中的函数的指针。并且看到在这个实例中没有声明结构,我很想知道函数 (myclass::*function)() 在调用时实际引用的是什么。
【解决方案3】:

这是正确的。上面已经引用了标准的相关部分,但这里是其价值的基本原理。

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 谢谢你的精彩解释,这件事对我来说真是个谜。
  • 看,这就是我第一次阅读它时的想法,但是由于您可以将 &access::print 的结果转换为 void (myclass::*)() 并不意味着您无论动态类型如何,都可以将其应用于静态类型 myclass 的对象吗?
  • 哇,去看看吧。你是绝对正确的,托德——即使它强制你写&amp;Derived::Foo 让它工作(然后它工作!),表达式的实际类型仍然是一个指向Base 成员的指针,你可以然后成功地使用它通过基指针访问受保护的成员。仔细阅读规范与此解释一致,MSVC 2008 和 2010、g++ 3.4 和 Comeau 也是如此。我试过谷歌搜索,但没有找到任何关于这个特殊极端案例的讨论 - 我想我会在 comp.std.c++ 上开始一个线程......
  • 也就是说,我认为限制的重点可能正如我在这个答案中所描述的那样,但他们只是做得还不够(忘记强制类型的指针为Derived?)。 protected 本身的意图在标准中非常清楚:“除非形成指向成员的指针(5.3.1),否则访问必须通过派生类本身的指针、引用或对象(或从该类派生的任何类)(5.2.5)” - 他们没有完全按照对成员指针的措辞应用这个限制,因为没有指针可以通过它访问它们......
  • 好的,这是一个已知问题,之前由 litb 提出过;见stackoverflow.com/questions/75538/hidden-features-of-c/…groups.google.com/group/comp.std.c++/browse_thread/thread/…groups.google.com/group/comp.lang.c++.moderated/browse_thread/…。虽然当时对此进行了一些讨论,但并没有发生,所以我再次提出了这个问题 - 希望它会引起一些反应(并可能成为 DR):groups.google.com/group/comp.std.c++/browse_thread/thread/…
猜你喜欢
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
  • 2021-04-26
  • 2011-10-19
  • 1970-01-01
  • 2019-10-07
  • 2012-12-01
  • 2012-06-04
相关资源
最近更新 更多