使用"using"关键字来定义基类变量

13

我正在学习WildMagic 5引擎(www.geometrictools.com),其中Vector<>类从Tuple<>类继承,后者有一个名为mTuple[]的特定大小的数组(由模板参数设置)。到目前为止还好,没有什么特别的。然而,在Vector类中,我看到以下内容:

protected:
    using Tuple<4,Real>::mTuple; 

现在我知道using关键字用于正确继承重载方法。在这种情况下,我一直认为变量在不输入上述代码的情况下就可以在派生类中使用。上述代码是必要的吗?还是只是为了更清晰明了?


2
+1,因为我读了“我知道using关键字用于正确继承重载方法”的内容后,我对你多了一点喜欢。 - Lightness Races in Orbit
@Tomalak:哈哈(字符计数填充) - Samaursa
3个回答

9

泛型编程与面向对象编程略有不同。

你的mTuple是非依赖名称的一个例子。就编译器而言,在处理模板定义时,编译器并不知道类模板继承了名为mTuple的数据成员。这对你来说可能很明显,但对编译器来说却不明显。此时,编译器对显而易见的事情毫不知情。

如果派生类模板的方法希望使用父类模板的某个成员,则需要明确告诉编译器。因此使用using

编辑

以上内容有点简洁。重要的是要记住,那些类模板不是类。它们是最终定义类的模板。在使用类模板定义类之前,该类模板还不是完全实际的。更重要的是,对于从某个其他类模板继承的类模板而言,该继承也不是完全实际的。除非明确告诉编译器,否则编译器不知道该继承。这就是为什么你会看到派生类模板通过using ParentClass<Type>::member(例如)导入父类的成员。

编辑 #2

Marshall Cline在他的C++-FAQ中讨论了这个话题,网址为http://www.parashift.com/c++-faq-lite/templates.html#faq-35.19

编辑 #3

(按要求)仅因为某些代码在你的编译器上编译通过,并不意味着它在每个编译器上都能编译通过(对于同一种语言)。编译器供应商会向语言添加自己的“功能”,有时是非常故意的,有时只是因为供应商自己搞砸了,有时是因为标准本身有问题。这个不太标准的编译器问题已经存在很长时间,影响了许多语言。当涉及到泛型编程时,问题显然非常普遍。

你可以做得很好(或者你认为是这样):打开所有标准警告,然后运行代码通过某个商业代码分析器,但你仍然可能没有可移植的代码。


但是在我的实现中,我没有这样做,一切都可以编译(也许我误解了你的解释)。当我查看WM5引擎的实现作为参考时,我才发现这个问题。 - Samaursa
一切都能够成功编译,正是因为“元组”(Tuple)的作者做了正确的事情。 - David Hammen
Tuple的作者是我 :)...就像我说的(虽然可能不够清楚),我写了自己的Vector和Tuple类,并想与WM5的实现进行比较,那时我注意到了这一点。 我从来没有为派生类输入using someVariable(我不是C++的新手...已经继承了相当长的时间!:) ,因此很好奇为什么需要这样做,特别是因为我的版本没有使用“using”关键字编译、运行和测试都非常良好。 此外,我不确定正如您所说,“Tuple的作者”与派生的Vector类有任何关系。 - Samaursa
如果您的Vector类模板没有使用Tuple类模板的任何成员,则不需要使用using语句。如果Vector确实通过this->member访问Tuple的成员,那么也不需要使用这样的using语句。另一方面,如果Vector访问Tuple的成员而没有任何限定符,那么您的代码就有问题了。如果您的有问题的代码可以编译并按预期运行,则您的编译器是有问题的(因此您不知道您的代码有问题)。 - David Hammen
@David:啊!这是我不知道的事情,感谢你指出来。所以如果我理解正确,MSVC编译器正在做比它应该做的更多的事情,并且我的代码可能会在另一个符合标准的编译器上出错(如果你根据你上面的评论再编辑一下你的答案,我就把它选为正确答案)。 - Samaursa
被选为答案。未来参考,请查看Johannes的回答(https://dev59.com/omw15IYBdhLWcg3w4_xL#6339291) - Samaursa

8

我相信一些人没有理解@David所说的话,他的观点是正确的。因此,我认为需要举一个例子:

template<typename T>
struct A { int a; };

template<typename T>
struct B : A<T> {
   void f() {
     a = 0; // #1
     this->a = 0; // #2
     B::a = 0; // #3
   }
   // using A<T>::a;
};

template<>
struct A<float> { }; // surprise!
#1会导致错误,因为在模板中,未限定查找不会查找依赖的基类。这是C++中的一个重要概念。如果有一个全局可见的a,那么a = 0将使用该a - 在依赖的基类中声明的成员将永远不会隐藏全局的a。为了告诉编译器查找依赖的基类,您必须限定名称。因此,this->a可以正常工作,B::a也是如此。
如果您放置了using A<T>::a,则告诉编译器在B的范围内声明一个成员名a。然后a = 0将直接在B中找到a。这也是可接受的修复方法。

好的,今天我学到了新的东西。但是提问者说他写了自己的版本而没有使用using声明,而且它工作正常... - Nemo
@Nemo,有些编译器可能不符合严格的标准规范。在他特定的代码中,他实际上可能并不需要使用声明,因为所有内容都已经使用了限定名称查找。我不知道他在代码中是否需要该声明,因为他没有提供更多的信息。 - Johannes Schaub - litb
谢谢您的解释。这确实是今天我学到的崭新的东西。就类而言,它非常简单。一个具有受保护成员数组的Tuple<>模板类被一个没有成员变量的Vector<>模板类继承。它可以在Visual C++ 2008上编译(不记得编译器版本了)。 - Samaursa
好的,如果您可以的话,我还有一个澄清问题(而不是开始一个新问题)。这对于非模板类也是必需的吗? - Samaursa

1
通常,您可以使用类似于以下声明来增加继承成员的访问权限。例如从protectedpublic
但是,您不能使用此语法来限制访问权限。并且您不能在声明为private的成员上使用它。
因此,唯一可能起作用的时间是如果Vector从Tuple<>继承,如下所示:
class Vector4 : private Tuple<4,Real>
{ ... }

在这种情况下,这个using声明将使mTuple受保护而不是私有。
在所有其他情况下,我相信它什么也不会做。
[编辑]
对于模板基类,我的看法是错误的。请参见JohannesDavid的答案。

在这种情况下,它是公共继承。 - Samaursa

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