c++模板类作为基类-模板子类访问基类成员的方法

今天遇到一个问题,在继承中,如果基类是一个模板类,那么子类直接访问模板类中的变量会报错,所以网上找了2个帖子,终于搞懂了,例子有点繁琐,但是慢慢看,还是能看懂的。

帖子1(总结比较全面,例子有点繁琐,但是很全面):

学习C++模板---模板类作为基类

使用模板类作为基类使用。

// 模板类-晋升.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <process.h>
#include <iostream>
using namespace std;

template< typename T, int nMax = 5 >
class CTestVector
{
public:
	CTestVector(void)
	{
		cout << "CTestVector(void)" << "nMax=" << nMax << std::endl;
		for (int i = 0; i < nMax; i++)
		{
			m_vec[i] = 0;
		}
	}

	CTestVector(T vec)
	{
		cout << "CTestVector(T vec)" << "vec="<< vec <<" nMax=" << nMax << std::endl;
		for (int i = 0; i < nMax; i++)
		{
			m_vec[i] = vec;
		}
	}

	void print(void)
	{
		for (int i = 0; i < nMax; i++)
		{
			std::cout << m_vec[i] << std::endl;
		}
	}
protected:

private:
	T m_vec[nMax];
};

//使用模板作为基类。
template< int nMax >
class CTestVectorInt : public CTestVector< int, nMax >
{
public:

	CTestVectorInt(void) //:CTestVector< int, nMax >(void)
	{
		cout << "CTestVectorInt(void )" << "nMax=" << nMax << std::endl;
	}

	CTestVectorInt(int vec) :CTestVector< int, nMax >(vec)
	{
		cout << "CTestVectorInt(int vec) " << "nMax=" << nMax << " vec=" << vec << std::endl;
	}

protected:

private:

};

/*在引入模板之后,我们面临一个新的问题,那就是如果继承自一个模板基类,是否跟继承一般的类有什么区别呢?

就是说,如果一个类继承自某个模板类,那么该类应该注意一些什么呢?其实,它与一般类的继承是很不一样的。

*/
template< typename T >
class Base
{
public:
	void printBase() { cout << "call Base printBase() ..." << endl; }
};

template< typename T >
class Derived : public Base<T>
{
public:
	void printDerived()
	{
		cout << "call Derived printDerived() ... " << endl;
		//printBase();		//会报错:error C3861: 'printBase': identifier not found

		/*方法一:this->, 除非Derived中也有一个printBase(),那么调用的就是Derived::printBase(),否则调用的是Base::printBase()*/
		this->printBase();	

		//方法二:声明为基类的方法Base<T>::printBase(),调用的是Base::printBase()
		Base<T>::printBase();	
		Base::printBase();

		//方法三:using 声明基类的printBase,调用的是Base::printBase()
		using Base::printBase;	
		printBase();

	}
};


//
//程序入口点。
//
int main()
{
	//使用继承模板。
	//CTestVectorInt< 2 > testVectorInt(); //-------记住不要这样写当构造函数无参的时候,不会调用任何构造函数直接报错
	//CTestVectorInt< 2 > testVectorInt = CTestVectorInt< 2 >() ;--------和下面的相同
	CTestVectorInt< 2 > testVectorInt;
	testVectorInt.print();


	//CTestVectorInt< 2 > testVectorIntVal= CTestVectorInt< 2 >(8) ;--------和下面的相同
	CTestVectorInt< 2 > testVectorIntVal(8);
	testVectorIntVal.print();

	//float实例。
	CTestVector< float, 3 > testFloat(3.14159f);
	testFloat.print();


	//double实例。
	double dA = 1.55233f;
	CTestVector< double, 6 > testDouble(dA);
	testDouble.print();

	//
	//暂停.
	//
	system("pause");

	return 0;
}

显然,在上述的LogSendMsg中,该类只有在模板形参Company被 实例化 之后才知道实际调用的是哪个Company。显然在上述的代码中,CampanyC是没有定义SendClear函数的,编译器不可能一一的去预先知道这样的情况,因为用户指不定就为某家公司定义了一个特化版本,会直接导致该函数失效。

        因此,编译器本着 “尽早报告错误”的可能性,在看到SendClear函数的时候,编译器不会去寻找该函数的定义,只当该函数没声明。结果就如上面的错误了。

注:这里就看出了模板基类与一般的基类的区别,一般的基类会沿着类的作用域一层一层的往上找,知道找到该函数的声明。但是模板类不会干这些无用功,是因为它知道在后续自己被 “实例化”之后,该函数可能根本就不存在(例如上述CompanyC的特化版本)。

       因此,对于编译器的这种  “不进入模板基类去寻找定义” 的行为,必须由用户告诉编译器该怎么干

1  可以使用this指针,即 this->sendClear 函数。

2   使用using声明,显式地告诉编译器,我要用的是基类的sendClear函数。 using Base::sendClear或者using Base<T>::sendClear

3  使用作用域符号,告诉编译器这个函数是哪个类的。Base<T>::sendClear

值得注意的是,上述三种途径,是针对该函数一定在基类中有定义才这么干,例如上述的CompanyC并没有定义sendClear函数,那么上述做法是失效的。

        总之,在使用模板基类时,对于基类中定义的名字,在派生类中必须显式地指明,因为在基类实例化之前,编译器对于基类的定义一无所知。。

帖子2:比较容易看懂,但是给出的解决方法只有一个:

下面的代码给了我一个编译错误'value'没有在这个范围内声明

Java

template<class T> 
struct Base { 
    int value; 
}; 
 
template <class T> 
struct Derived : public Base<T> { 
    int getValue() { return value; } 
}; 

我觉得很奇怪

  • 如果 Derived继承自 Base<std::string> ,代码编译,
  • 如果我 return Base<T>::value , 代码编译。

为什么代码不能按原样编译? Derived<T>::getValue() 的范围内没有以何种方式声明“值(value)”? ?

请您参考如下方法:

因为value是一个非限定名称,在名称查找的第一阶段,编译器不会知道这是一个从基类继承的数据成员(它还没有实例化Base<T>)。因此,它将搜索全局命名空间,并没有找到名为 value 的变量。 ;因此,它会发出错误。

这是解决此问题的典型方法:

Java

template <class T> 
struct Derived : public Base<T> { 
    int getValue() { return this->value; } 
    //                      ^^^^^^ 
}; 

显式取消引用 this告诉编译器后面的名字是一个(可能继承的)数据成员的名字,并且查找应该延迟到成员函数被实际实例化的地方。当然,您的解决方案是:

Java

return Base<T>::value; 

同样好,因为它还告诉编译器 value继承自基类 Base<T> .

关于源自 Base<std::string> 的问题,编译器可以立即去查找是否Base<std::string>包含一个名为 value 的数据成员(因为它不依赖于任何模板参数)如果是这种情况,它将能够确定表达式是格式正确的。

但是,如果您的基类是 Base<T> , 其中 T在名称查找的第一阶段是未知的,编译器无法判断 value是(Base 的特化为不同的 T s 甚至可能根本没有 value)。

C++11 标准第 14.6/3 段:

In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member. [...] [Example:

Java

struct A { 
    struct B { / ... / }; 
    int a; 
    int Y; 
}; 
 
int a; 
 
  template<class T> struct Y : T { 
    struct B { / ... / }; 
    B b; // The B defined in Y 
    void f(int i) { a = i; } // ::a 
    Y* p; // Y<T> 
  }; 
 
Y<A> ya; 

The members A::B, A::a, and A::Y of the template argument A do not affect the binding of names in Y<A>. —end example ]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值