C++无法从派生类中调用基类方法

C++ Cannot call base class method from within derived class

本文关键字:调用 基类 类方法 派生 C++      更新时间:2023-10-16

这是我的第一个问题,我希望我做的每件事都正确。

我试图从boost元组派生一个类。Boost的元组提供了一个get()模板方法来访问各个字段。有趣的是,我不能在派生类中使用该方法。

以下代码显示了问题:

#include <iostream>
#include <boost/tuple/tuple.hpp>
using namespace std;
template<typename A>
class Derived : public boost::tuple<A>
{
public:
    Derived() : boost::tuple<A>() {}
    A& getVal0()
    {
        return get<0>();
        // does not compile:
        //error: 'get' was not declared in this scope
        return boost::tuple<A>::get<0>();
        // does not compile
        //error: expected primary-expression before ')' token
        return boost::tuples::get<0>(*this);
        //works
    }
};  
int main() {
    Derived<int> a;
    a.get<0>() = 5;
    cout << a.get<0>() << endl; 
    cout << a.getVal0() << endl; 
    return 0;
}

我想知道为什么我可以从主功能访问get<0>()方法

a.get<0>() = 5;

但不是来自CCD_ 2方法:

error: 'get' was not declared in this scope

第二个返回行是我尝试将方法调用范围定为基类:

return boost::tuple<A>::get<0>();

这会产生不同的错误

error: expected primary-expression before ')' token

调用外部函数`boost::tuples::get<0>(*this)有效。这个变通方法对我来说是可以的。但我仍然想知道为什么我现在不能使用元组方法。

boost文档中有一条关于Visual C++的通知

注意!MS Visual C++编译器不支持成员get函数。此外,编译器在查找没有显式命名空间限定符的非成员get函数时遇到了问题。因此,在编写应该使用MSVC++6.0编译的代码时,所有get调用都应该被限定为:tuples:get(a_tople)。

但我使用的是GCC 4.5.2&4.8.1

提前感谢

假设基类中有一个get<I>()成员函数模板,您可能想要使用

this->template get<0>()

this部分是使其成为依赖查找所必需的(您也可以使用适当的类限定,但这有点麻烦,而且没有必要,除非您隐藏基类名称)。template部分是告诉编译器依赖名称(get)恰好是一个模板所必需的。

需要this(或某些其他资格)和template的主要原因是模板的两阶段编译模型:

  • 任何不直接依赖于某种形式的模板参数的名称都只在第一阶段查找,即在定义模板的上下文中查找。由于模板参数是未知的,因此基类的确切布局也是未知的(它可能是专门的),基类中的任何名称都会被忽略。使用使名称依赖于模板参数的任何限定,例如,使用this->,将查找移动到第二阶段,即当模板被实例化时
  • 一旦名称是依赖的,那么在第一阶段解析模板时,如果表达式包含<字符,即当模板参数未知时,就会产生歧义:<可以是成员函数调用的显式模板参数的开始,也可以是小于运算符。由于很少明确提及模板参数(至少,在制定这些规则时很少提及),因此默认情况下,它被认为是小于运算符。要声明该名称实际上是一个具有明确指定的模板参数的成员函数模板,需要在其前面加上关键字template(与需要typename的类型非常相似)

我发现这个问题很有帮助。在大量继承结构的代码中,在类内部引用基类中的对象的能力非常重要。随着代码越来越成熟,模板化类很可能需要这种功能。在2011年之前,这很容易。然而,它变得越来越具有挑战性;从这个问题和答案中,我意识到我的解决方案走错了方向。

下面的代码(一个通用的解决方案,也是适用于这里给出的示例的版本),受到给出的答案的刺激,是我所知道的最简单的方法。其他人可能也会觉得这很有帮助。它比这里的答案更进一步,因为它还处理基类中定义的TYPES。这种方法也适用于非模板类。

#include <iostream>
#include <boost/tuple/tuple.hpp>
/// Using inheritance within a template class
template<typename U>
struct A {
    // a parameter dependent type
    typedef U ARG;
    // a member variable
    U arg = 7;
    // a member function
    void foo()
    {
        std::cout << "A::foo n";
    }
};
template<typename T>
struct B : A<T> {
    // a shell separating A from C to represent a more complex hierarchy
};
template<typename T>
struct C : B<T> {
    // a standardized name for the local type and context for the derived class
    using This = C;
    /// C is a derived class - with distant base members and types accessible through a parameter dependent base class
    void foo1()
    {
        /// Using inherited objects within a template class does not need to know the base class or introduce a using or typedef
        /// However, one does need to tell the compiler that the names are to be interpreted in the local context
        
        /// Using a base type ARG
        typename This::ARG z = 3;
        /// Using a base function foo()
        This::foo();
        /// Using a base member arg
        This::arg = z;
        
        // this->arg and this->foo also work for the last two, auto-converting "this" to a pointer to the relevant base class
        
        std::cout  << "C::foo1 finds (without referencing any base class):"
            " the type ARG"
            ", the variable arg"
            ", and the function foo"
            ", all defined in a distant base class A<U> with U set to double. n";
    }
};
// the example from the question
template<typename A>
class Derived : public boost::tuple<A>
{
public:
    A& getVal0()
    {
        // inform the compiler of the local context for get<>()
        return Derived::get<0>();
    }
};
int main()
{
    /// the solution to the example 
    Derived<int> a;
    a.get<0>() = 5;
    std::cout << a.get<0>() << std::endl;
    std::cout << a.getVal0() << std::endl;
    std::cout << "n////////////////////////Generic Examples///////////////////nn";
    /// Using inheritance with a template class
    C<double> c;
    C<double>::ARG x = 99;
    
    std::cout 
        << "the variable c.arg: " << c.arg 
        << ", the type C<double>::ARG "<< x 
        << ", and the function c.foo() "; c.foo();
    std::cout 
        << "are all directly accessible from external code.nn";
    c.foo1();
}
/* Output:
5
5
////////////////////////Generic Examples///////////////////
the variable c.arg: 7, the type C<double>::ARG 99, and the function c.foo() A::foo
are all directly accessible from external code.
A::foo
C::foo1 finds (without referencing any base class): the type ARG, the variable arg, 
and the function foo, all defined in a distant base class A<U> with U set to double.
*/

这种方法简单有效。正如typename需要说明什么时候类型是类型一样,现代C++似乎强制要求您告诉它(至少在模板中),通过在名称前面加上派生类名,名称是在派生类之上的层次结构中定义的。这就是所需的全部内容,对于非模板化的类显然可以做到这一点而不会出错。

我曾经通过先开发一个实例,然后在工作时添加template<class ..>来开发模板化类。这停止了工作,因为访问基类成员的挑战;这不再是一个问题。我只是在最初的类定义中使用This::语法。

此代码适用于C++2020和2011。