奇怪的编译器错误和模板继承

5

有人能解释一下为什么这段代码:

class safe_bool_base
{ //13
    protected:

        typedef void (safe_bool_base::*bool_type)() const;

        void this_type_does_not_support_comparisons() const {} //18

        safe_bool_base() {}
        safe_bool_base(const safe_bool_base&) {}
        safe_bool_base& operator=(const safe_bool_base&) { return *this; }
        ~safe_bool_base() {}
};

template <typename T=void> class safe_bool : public safe_bool_base
{
    public:

        operator bool_type() const
        {
            return (static_cast<const T*>(this))->boolean_test() ? &safe_bool_base::this_type_does_not_support_comparisons : 0;
        }

    protected:

        ~safe_bool() {}
};

template <> class safe_bool<void> : public safe_bool_base
{
    public:

        operator bool_type() const
        {
            return (boolean_test() == true) ? &safe_bool_base::this_type_does_not_support_comparisons : 0; //46
        }

    protected:

        virtual bool boolean_test() const = 0;
        virtual ~safe_bool() {}
};

产生以下编译错误?
c:\project\include\safe_bool.hpp(46) : error C2248: 'safe_bool_base::this_type_does_not_support_comparisons' : cannot access protected member declared in class 'safe_bool_base'
c:\project\include\safe_bool.hpp(18) : see declaration of 'safe_bool_base::this_type_does_not_support_comparisons'
c:\project\include\safe_bool.hpp(13) : see declaration of 'safe_bool_base'

由于safe_bool模板都是从safe_bool_base派生而来的,我不明白为什么不能访问基类的受保护成员。

我有什么遗漏吗?


1
这是一个很好的问题。建议您在问题中添加“protected”,“base”,“derived”标签,以便在搜索/参考中出现。 - Chubsdad
@Chubsdad:谢谢。我只能再添加一个标签。(据我所知,最多只能添加5个标签。) - ereOn
3个回答

9

这可能会有所帮助(在非模板情况下也可以重现)

struct A{
protected:
    void f(){}
};

struct B : A{
    void g(){&A::f;}        // error, due to Standard rule quoted below
};

int main(){
}

在VS中出现"'A::f' : cannot access protected member declared in class 'A'"的错误
相同的代码,在Comeau中会显示以下错误信息:
"ComeauTest.c", line 7: error: protected function "A::f" (declared at line 3) is not accessible through a "A" pointer or object void g(){&A::f;} ^
"ComeauTest.c", line 7: warning: expression has no effect void g(){&A::f;}
为了实现所需的目的,以下是修复后的代码。
struct A{
protected:
    void f(){}
};

struct B : A{
    void g(){&B::f;}        // works now
};

int main(){
}

因此,为什么第一个代码片段不起作用呢?
这是因为C++ Standard03中的以下规则:
11.5/1-“当派生类的友元或成员函数引用基类的受保护非静态成员函数或受保护非静态数据成员时,在适用于第11.102条的早期描述之外还应用了访问检查。除非形成成员指针(5.3.1),否则访问必须通过指向、引用或派生类本身的对象(或从该类派生的任何类)进行(5.2.5)。如果访问是为了形成成员指针,则嵌套名称说明符必须命名派生类(或从该类派生的任何类)。”
因此,请将运算符函数中的返回值更改如下:
return (boolean_test() == true) ? &safe_bool<void>::this_type_does_not_support_comparisons : 0; //46 

return (static_cast<const T*>(this))->boolean_test() ? &typename safe_bool<T>::this_type_does_not_support_comparisons : 0; 

编辑2:请忽略我的解释。David是正确的。以下是要点。

struct A{
protected:
    int x;
};

struct B : A{
    void f();
};

struct C : B{};

struct D: A{            // not from 'C'
};

void B::f(){
    x = 2;         // it's own 'A' subobjects 'x'. Well-formed

    B b;
    b.x = 2;       // access through B, well-formed

    C c;
    c.x = 2;       // access in 'B' using 'C' which is derived from 'B', well-formed.

    D d;
    d.x = 2;       // ill-formed. 'B' and 'D' unrelated even though 'A' is a common base
}

int main(){} 

1
@Oli Charlesworth:我猜他们希望派生类只能访问自己的基类受保护成员,而不是其他类的。因此,这基本上确保了派生类'B'的obj1可以修改其自己的基类'A'的受保护成员,而不是类型为'B'的另一个obj2。它有点像私有和公共之间的东西。 - Chubsdad
2
@Chubsdad:我不确定我是否理解了你的例子,理性是正确的,因为允许这样做会破坏受保护的访问限定符,但不是与同一类型B的其他对象,而是与其他类型。考虑 struct A {...}; struct B : A {...}; struct C : A {...}; void B::break( X& x ) { x.*(&X::f)(); }。如果 XB,则代码应该编译并工作,但如果 XA,那么该方法可以被调用一个 C 的实例(与 B 无关),它将从一个不相关的类中调用非公共成员。 - David Rodríguez - dribeas
2
通过强制你以最派生的类型获取指针,它确保你不能这样做。C c; void B::break() { c.*(&B::f)(); } 会产生编译错误,因为成员指针是指向 B 而不是 C - David Rodríguez - dribeas
@dribeas的David Rodríguez和@Chubsdad:非常感谢你们的回答。我总是惊讶于有些人能够引用解释一切的标准相关部分。 - ereOn
1
@ereOn:这就是多人审阅的好处。每个人都会记得或找到一些你不该做的标准细节,你只需要让那个人看到问题。如果litb回答最好了,他几乎可以自行处理所有问题 :-) - Steve Jessop
显示剩余6条评论

1

我认为这与模板无关。您的示例代码可以简化为以下内容,仍然会产生相同的错误:

class A
{
    protected:
        typedef void (A::*type)() const;
        void foo() const {}
};


class B : public A
{
    public:
        operator type() const
        {
            return &A::foo;
        }
};

我相信问题在于您不能在公共接口中返回对受保护成员的成员函数指针。 (编辑:不是真的...)


有趣。但是如果我删除最后一个模板声明,编译器不会抱怨,并且仍然有一个返回safe_bool_base::this_type_does_not_support_comparisons地址的方法。 - ereOn
似乎很合理。将方法设为受保护的,意味着你只想让“邻居”访问你的宝箱。在这个例子中,邻居只是把钥匙(指向受保护方法的指针)随便给了任何人。 - Patrick
@Patrick:我不同意。如果这是真的,它也应该适用于成员变量。但据我所知,没有什么能阻止我在公共接口中返回privateprotected成员变量的地址。 - ereOn
@Patrick:这正是我所想的。但是,如果你将operator bool_type()移动到基类的public接口中,一切似乎都可以编译通过。 - Oliver Charlesworth
@ereOn:关于删除最后一个模板定义的问题;由于您不再拥有该模板的实例化,因此没有需要编译的代码。如果您以另一种方式实例化该模板(例如class safe_bool<int> v;),则会出现相同的错误。 - Oliver Charlesworth

0

Chubsdad的回答澄清了您关于为什么模板特化会出错的问题。

现在,以下C++标准规则

14.7.2/11 通常的访问检查规则不适用于用于指定显式实例化的名称。[注:特别是,在函数声明符中使用的模板参数和名称(包括参数类型、返回类型和异常规范)可能是私有类型或对象,这些类型或对象通常是不可访问的,而模板可能是成员模板或成员函数,这些成员通常也是不可访问的。——结尾注释]

将解释为什么通用模板实例化不会引发错误。即使您具有私有访问限定符,它也不会引发错误。


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