【问题标题】:Weird compiler error and template inheritance奇怪的编译器错误和模板继承
【发布时间】:2010-09-07 09:41:25
【问题描述】:

谁能解释一下为什么这个代码:

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,我不明白为什么不能访问基类的受保护成员。

我错过了什么吗?

【问题讨论】:

  • 这是个好问题。建议您在问题中添加标签“受保护”、“基础”、“派生”,以便在搜索/参考中出现
  • @Chubsdad:谢谢。我只能再添加一个标签。 (5 是 afaik 的最大标签数。)

标签: c++ templates protected derived safe-bool-idiom


【解决方案1】:

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

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

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

int main(){
}

VS 给出 "'A::f' : 无法访问 在类中声明的受保护成员 'A'"

对于相同的代码,Comeau 给出了

“ComeauTest.c”,第 7 行:错误: 受保护的函数“A::f”(声明于 第 3 行)是 无法通过“A”指针或对象访问 void g(){&A::f;} ^

“ComeauTest.c”,第 7 行:警告: 表达式无效 void g(){&A::f;}

这是达到预期目的的固定代码

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

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

int main(){
}

那么,为什么第一个代码 sn -p 不起作用?

这是因为 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:请忽略我的解释。大卫是对的。归根结底是这样的。

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(){} 

【讨论】:

  • 这听起来像是正确的答案!知道该规则背后的基本原理是什么?
  • @Oli Charlesworth:我猜他们希望派生类只能访问自己的基类受保护成员,而不是其他受保护成员。所以这基本上确保了派生类'B'的obj1可以修改它自己的基类'A'的受保护成员,而不是另一个'B'类型的obj2。它介于私人和公共之间。
  • @Chubsdad:我不确定我是否按照您的示例进行操作,理性是正确的,因为允许这会破坏受保护的访问限定符,但不是针对相同类型 B 的其他对象,而是针对其他类型.考虑struct A {...}; struct B : A {...}; struct C : A {...}; void B::break( X&amp; x ) { x.*(&amp;X::f)(); }。如果XB,则代码应该可以编译并工作,但如果XA,那么可以使用C 的实例调用该方法(与B 无关),它会调用一个来自不相关类的非公共成员。
  • ... 通过强制您采用最派生类型的指针,它确保您不能这样做。 C c; void B::break() { c.*(&amp;B::f)(); } 将产生编译错误,因为成员指针指向 B 而不是 C
  • @ereOn:这就是拥有很多眼睛为你做的事情。每个人都记得或找到一些你不会做的标准,你只需要那个人看到问题。更好的是,如果 litb 回答,他几乎可以自己处理所有事情:-)
【解决方案2】:

我认为这与模板无关。您的示例代码可以简化为这样,它仍然给出等效的错误:

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的地址
  • 似乎合乎逻辑。通过使方法受到保护,您表明您只想信任您的“邻居”访问您的宝箱。在这个例子中,邻居只是简单地把宝箱的钥匙给任何人(指向受保护方法的指针)
  • @Patrick:我不同意。如果这是真的,它也应该适用于成员变量。但据我所知,没有什么能阻止我在公共接口中返回 privateprotected 成员变量的地址。
  • @Patrick:我就是这么想的。但是如果你将operator bool_type()移动到基类的public接口中,一切似乎都可以编译了。
  • @ereOn:关于删除最后一个模板定义;您不再拥有模板的实例化,因此无需编译代码。如果您以另一种方式实例化模板(例如class safe_bool&lt;int&gt; v;),则会出现相同的错误。
【解决方案3】:

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

下面是 C++ 标准规则

14.7.2/11 通常的访问检查规则不适用于用于明确指定的名称
实例化
。 [注意:特别是函数中使用的模板参数和名称
声明符(包括参数类型、返回类型和异常规范)可能是
通常无法访问的私有类型或对象,并且模板可能是一个
通常无法访问的成员模板或成员函数。 ——尾注]

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 2016-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多