8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

为什么必须通过 this 指针访问模板基类成员?

seanoc5 1月前

38 0

如果下面的类不是模板,我可以在派生类中简单地使用 x。但是,对于下面的代码,我必须使用 this->x。为什么?模板类基{受保护...

如果下面的类不是模板,我可以直接 x derived 类中使用。但是,对于下面的代码,我 必须 使用 this->x 。为什么?

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}
帖子版权声明 1、本帖标题:为什么必须通过 this 指针访问模板基类成员?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由seanoc5在本站《arrays》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 在继承期间被隐藏。您可以通过以下方式取消隐藏 x

    template <typename T>
    class derived : public base<T> {
    
    public:
        using base<T>::x;             // added "using" statement
        int f() { return x; }
    };
    
  • (原始答案来自 2011 年 1 月 10 日)

    我想我已经找到了答案: GCC 问题:使用依赖于模板参数的基类成员 。答案并不是特定于 gcc 的。


    更新: 响应 mmichael 的评论 ,来自 C++11 标准 草案 N3337

    14.6.2 依赖名称 [temp.dep]
    [...]
    3 在类或类模板的定义中,如果基类依赖于模板参数,则在类模板或成员的定义点或者类模板或成员的实例化期间,在非限定名称查找期间不会检查基类范围。

    我不知道 “因为标准是这么说的” 是否 Steve Jessop 的出色回答 和其他人指出的那样,后一个问题的答案相当冗长且有争议。不幸的是,当涉及到 C++ 标准时,通常几乎不可能给出一个简短而完整的解释来说明为什么标准会要求这样做;这也适用于后一个问题。

  • Arne 1月前 0 只看Ta
    引用 4

    @SteveJessop 仅在非限定名称查找中不搜索基类。换句话说,非限定名称查找(无论是否查找依赖名称)都不会查找依赖基类。

  • 哇,我们可以说百科全书吗?击掌 不过,有一个微妙的点:“如果 Foo 实例化时使用某种具有数据成员 A 的类型,而不是嵌套类型 A,那么这是执行实例化的代码中的错误(阶段 2),而不是模板中的错误(阶段 1)。” 最好说模板没有格式错误,但这仍然可能是模板编写者的错误假设或逻辑错误。如果标记的实例化实际上是预期的用例,那么模板就是错误的。

  • Nash 1月前 0 只看Ta
    引用 6

    @jalf:有没有像 C++QTWBFAETYNSYEWTKTAAHMITTBGOW 这样的东西 - \'经常被问到的问题,但你不确定是否想知道答案,而且还有更重要的事情要做\'?

  • 简短的回答:为了创建 x 一个依赖名称,以便查找被推迟到知道模板参数为止。

    长答案:当编译器看到模板时,它应该立即执行某些检查,而无需查看模板参数。其他检查则推迟到参数已知时执行。这称为两阶段编译,MSVC 不这样做,但这是标准所要求的,并由其他主要编译器实现。如果您愿意,编译器必须在看到模板后立即编译模板(编译为某种内部解析树表示),并将实例化的编译推迟到稍后。

    对模板本身而不是其特定实例执行的检查要求编译器能够解析模板中代码的语法。

    在 C++(和 C)中,为了解析代码的语法,有时需要知道某个东西是否是类型。例如:

    #if WANT_POINTER
        typedef int A;
    #else
        int A;
    #endif
    static const int x = 2;
    template <typename T> void foo() { A *x = 0; }
    

    如果 A 是类型,则声明一个指针(除了遮蔽全局变量之外没有其他效果 x )。如果 A 是对象,则执行乘法(除非某些运算符重载,否则赋值给右值是非法的)。如果是错误的,则必须 在阶段 1 中 ,标准将其定义为 模板中的 ,而不是模板的某些特定实例中的错误。即使模板从未被实例化,如果 A 是, int 则上述代码格式不正确,必须进行诊断,就像它 foo 根本不是模板而是普通函数一样。

    现在,标准规定依赖于模板参数的名称必须在阶段 1 中可解析。 A 这里不是依赖名称,无论类型如何,它都指的是同一个东西 T 。因此,需要在定义模板之前定义它,以便在阶段 1 中找到和检查它。

    T::A 将是一个依赖于 T 的名称。我们不可能在第 1 阶段知道这是否是一种类型。最终将 T 在实例化中用作的类型很可能尚未定义,即使定义了,我们也不知道将使用哪种类型作为模板参数。但我们必须解决语法问题,以便执行宝贵的第 1 阶段检查以查找格式错误的模板。因此,标准对依赖名称有一条规则 - 编译器必须假定它们是非类型,除非使用 限定来 typename 指定它们类型,或在某些明确的上下文中使用。例如,在 中 template <typename T> struct Foo : T::A {}; , T::A 用作基类,因此显然是一种类型。如果 Foo 用具有数据成员的某种类型而不是嵌套类型 A 来实例化 A ,那么这是执行实例化(第 2 阶段)的代码中的错误,而不是模板(第 1 阶段)中的错误。

    但是,如果类模板具有依赖基类,情况会怎样呢?

    template <typename T>
    struct Foo : Bar<T> {
        Foo() { A *x = 0; }
    };
    

    A 是否是依赖名称?对于基类, 任何 名称都可以出现在基类中。因此,我们可以说 A 是一个依赖名称,并将其视为非类型。这会产生不良影响,即 每个名称 都是依赖的,因此 每种类型 (内置类型除外)都必须经过限定。在 Foo 内部,您必须编写:

    typename std::string s = "hello, world";
    

    因为 std::string 将是一个从属名称,因此除非另有说明,否则将假定为非类型。哎哟!

    允许您首选代码 ( return x; ) 的第二个问题是,即使 Bar 之前已定义 Foo ,并且 x 不是该定义中的成员,有人以后可能会 Bar 为某些类型 定义 Baz ,例如 Bar<Baz> 确实具有数据成员 x ,然后实例化 Foo<Baz> 。因此,在该实例中,您的模板将返回数据成员而不是返回全局 x 。或者相反,如果 的基本模板定义 Bar x ,他们可以定义一个没有它的特化,并且您的模板将在 中寻找要 x 返回的 Foo<Baz> 。我认为这被认为与您的问题一样令人惊讶和沮丧,但它是 默默地 令人惊讶,而不是抛出一个令人惊讶的错误。

    为了避免这些问题,标准实际上规定,除非明确要求,否则不会考虑搜索类模板的依赖基类。这样一来,所有内容就不会因为可以在依赖基类中找到而成为依赖项。它还会产生您所看到的不良影响 - 您必须限定基类中的内容,否则找不到它。有三种常见的方法可以进行 A 依赖:

    • using Bar<T>::A; 在类中 - A 现在指的是中的某物 Bar<T> ,因此是依赖的。
    • Bar<T>::A *x = 0; 在使用时 - 再次强调, A 肯定在 Bar<T> 。这是乘法,因为 typename 没有使用,所以可能是一个不好的例子,但我们必须等到实例化才能知道是否 operator*(Bar<T>::A, x) 返回右值。谁知道呢,也许确实如此……
    • this->A; 在使用时 - A 是一个成员,所以如果它不在中 Foo ,它必须在基类中,标准再次表明这使它具有依赖性。

    两阶段编译既繁琐又困难,并且会引入一些令人惊讶的额外代码要求。但就像民主一样,除了所有其他方式外,它可能是最糟糕的做事方式。

    您可以合理地争辩说,在您的示例中, return x; 是基类中的嵌套类型,则 x 没有意义 this-> 。在某种程度上,您是解决不适用于您的情况的问题的附带损害的受害者,但仍然存在这样的问题:您的基类可能会在您之下引入影响全局变量的名称,或者没有您认为它们有的名称,而是找到了全局变量。

    您可能还会认为,默认设置应该与从属名称相反(除非以某种方式指定为对象,否则假设类型),或者默认设置应该更敏感于上下文(in std::string s = ""; , std::string 可以读作类型,因为其他任何东西都没有语法意义,尽管 std::string *s = 0; 是模棱两可的)。同样,我不知道规则是如何达成一致的。我的猜测是,所需的文本页数可以减轻创建大量特定规则的负担,这些规则规定哪些上下文采用类型,哪些上下文采用非类型。

  • 发生这种情况是因为两阶段名称查找(并非所有编译器都默认使用)和依赖名称。除了在

  • @Ed Swangren:抱歉,我在发布这个问题时错过了提供的答案。在此之前我已经寻找答案很长时间了。

返回
作者最近主题: