栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++类模板中如何调用其基类模板中的函数(模板中使用虚函数)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++类模板中如何调用其基类模板中的函数(模板中使用虚函数)

文章目录
    • 1.模板实现的virtual多态问题
    • 2.C++模板的名字查找
    • 3.错误解法与正确解法
    • 4.模板实现多态的其他方式

1.模板实现的virtual多态问题
  • eg:下面实现模板多态的方式1有问题,解决办法可以看:3.错误解法与正确解法
  • 若不理解下面的代码,请看:C++类内存分布 通过Visual studio 2013查看
#include 
#include 
#include 
#include 

template 
class Base {
public:
    virtual const std::string& Method() const = 0;
};

template 
class Base2 : public Base {
public:
    void Func() const {
        std::cout << "Func:" << Method() << std::endl;
    }
};

class Derived : public Base2 {
public:
    const std::string& Method() const {
        static const std::string s("Derived");
        return s;
    }
};


int main(int argc, const char* argv[])
{
    Derived d;
    d.Func();

    system("pause");


}
  • 测试:
/home//test/broudcast/template.cpp: In member function ‘void Base2::Func() const’:
/home//test/broudcast/template.cpp:16:33: error: there are no arguments to ‘Method’ that depend on a template parameter, so a declaration of ‘Method’ must be available [-fpermissive]
   16 |         std::cout << "Func:" << Method() << std::endl;
      |                                 ^~~~~~
/home//test/broudcast/template.cpp:16:33: note: (if you use ‘-fpermissive’, G++ will accept your code, but allowing the use of an undeclared name is deprecated)

原因:见2.C++模板的名字查找

  • Method与模板参数无关,因此是无依赖名字,编译器会在全局变量中查找Method,不会去Base类模板中查找Method,只会在Base2的定义体中及外围作用域查找Method。

如何解决呢?

  • 见:3中相关的解决办法
2.C++模板的名字查找

在编译模板的时候,编译器会分两个阶段去解析遇到的名称,

  • 第一个阶段解析不依赖于模板参数的名称
  • 第二个阶段解析依赖于模板参数的名称
  • eg:对于非类模板而言
    可以看到f()的返回类型变成了int
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include          // std::this_thread::sleep_for
#include          // std::chrono::seconds
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include       // std::ofstream
#include 
#include 
#include 
#include 
#include  //使用abi


using namespace std;

class X {
public:
	typedef int E;
};
 
typedef double E;
 
class Y : public X {
public:
	E f() {}
};
 
int main(){
 
	Y y;
	cout< 
  • 测试:

  • eg:类模板而言
    因为f()不依赖与模板参数T,所以它在第一阶段就会被解析,而它的基类X在第二阶段才会被解析,所以解析f()的时候只能看到全局作用域里的typedef double E

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include          // std::this_thread::sleep_for
#include          // std::chrono::seconds
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include       // std::ofstream
#include 
#include 
#include 
#include 
#include  //使用abi


using namespace std;


template class X {
public:
	typedef int E;
};
 
typedef double E;
 
template class Y : public X {
public:
	E f() {}
};
int main(){
 
	Y y;
	cout< 
  • 测试:

  • eg:这个例子告诉我们,在类模板中,如果需要使用成员函数,需要通过this指针或其他方式来指定,而不能像在普通类中那样省略到this指针

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include          // std::this_thread::sleep_for
#include          // std::chrono::seconds
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include       // std::ofstream
#include 
#include 
#include 
#include 
#include  //使用abi


using namespace std;

void g() { cout<<"gobal::g()"< class X {
public:
	typedef int E;
	void g() { cout<<"X::g()"< class Y : public X {
public:
	E f() { g(); this->g(); }
};


int main(){
 
	Y y;
     cout< 
  • 测试:
    (1)第一种方式调用了全局的g(),
    (2)第二种方式调用了基类的g(),
    (3)原因在于当编译器遇到第一种方式的g()时,发现它没有表现出要依赖于模板参数,所以认为它不依赖于模板参数,因而在第一阶段就进行了解析,此时X尚未解析,所以g()是全局的g();
    通过第二种方式调用时,用this指针指出g()是依赖于当前对象的,**也就依赖于模板参数,因而会在第二阶段解析,**那时基类也会先于Y进行了解析,所以this–>g()调用了基类的g()。

总结,C++在对模板类和模板函数进行名字查找时,会分成两次进行:

  • 对于与模板参数无关的名字,或称无依赖名字,编译器会在第一阶段进行查找。
  • 对于与模板参数有关的名字,或称有依赖名字,编译器会在第二阶段进行查找。
3.错误解法与正确解法

错误解法1:指定基类类型调用

  • 假如我们调用Method时指定基类类型,这样Method就变成有依赖名字了,是否可行?
template 
class Base2: public Base {
public:
    void Func() const {
        std::cout << "Func:" << Base::Method() << std::endl;
    }
};
  • 测试:因为我们指定了Method的类型,对Method的调用就是静态绑定,没有了动态绑定的效果,我们运行的Method就是基类版本,也就是没有定义的那个版本。
/tmp/ccgToaEX.o: In function `Base2::Func() const':
test.cpp:(.text._ZNK5Base2IdE4FuncEv[Base2::Func() const]+0x12): undefined reference to `Base::Method() const'
collect2: ld returned 1 exit status

正确解法1:using基类中的名字

  • 我们可以手动using基类中的名字,让名字查找时能看到Method,这样还表明Method是Base中的成员,也就意味着Method依赖T,它就是一个有依赖名字,会推迟到实例化时再查找(第二阶段查找)。这样我们仍然能让Method的调用是动态绑定:
template 
class Base2: public Base {
public:
    using Base::Method;
    void Func() const {
        std::cout << "Func:" << Method() << std::endl;
    }
};
  • 测试:
Func:Derived

正确解法2:使用this调用

  • 另一种解法是显式使用this,这样也可以将Method变成有依赖名字:
template 
class Base2: public Base {
public:
    void Func() const {
        std::cout << "Func:" << this->Method() << std::endl;
    }
};
  • 测试:
Func:Derived
4.模板实现多态的其他方式

模板实现静态多态的其他方式

  • 一般,使用虚函数和继承实现的是动多态,即在运行期间确定调用者的类型。使用模板,可以实现静多态,在编译期间确定调用者的类型。
使用虚函数可以这样实现
class BaseType
{
public:
	virtual void action1(){...}
    virtual void action2(){...}
}

class SubType1 : BaseType
{
public:
    virtual void action1(){...}
    virtual void action2(){...}
}

class SubType2 : BaseType
{
public:
    virtual void action1(){...}
    virtual void action2(){...}
}

// 统一处理
void DoAction1(BaseType const& type)
{
    type.action1();
}

void DoAction2(BaseType* type)
{
    type->action2();
}

// 可以处理异类集合
void DoActions(std::vector list) 
{
    ...
}


如果使用模板来实现,就会变成这样
class SubType1
{
public:
    void action1(){...}
    void action2(){...}
}

class SubType2
{
public:
    void action1(){...}
    void action2(){...}
}

// 统一处理
template
void DoAction1(BaseType const& type)
{
    type.action1();
}

template
void DoAction2(BaseType* type)
{
    type->action2();
}

// 不可以处理异类集合,因为要在编译期间确定,vector只能为一个类型的集合
template
void DoActions(std::vector list)  // 处理异类集合会报错
{
    ...
}

eg:使用模板实现Bridge Pattern

  • 用一个指针引用具体的实现,然后把所有的调用都委托给这个**(包含这个指针)的类**。
使用多态实现:
// 实现的接口基类
class Implementation
{
	virtual operationA() = 0;
    virtual operationB() = 0;
    virtual operationC() = 0;
}

// 实现A
class ImplementationA : Implementation
{
    virtual operationA(){...}
    virtual operationB(){...}
    virtual operationC(){...}
}

// 实现B
class ImplementationB : Implementation
{
    virtual operationA(){...}
    virtual operationB(){...}
    virtual operationC(){...}
}

class Bridge
{
public:
    // 通过将body指向不同的实现类来调用不同的实现
    Implementation* body;
    
    void operationA()
    {
        body->operationA();
    }
    
    void operationB()
    {
        body->operationB();
    }
}


使用模板实现,即使用静多态:
// 实现的接口基类
class Implementation
{
	virtual operationA() = 0;
    virtual operationB() = 0;
    virtual operationC() = 0;
}

// 实现A
class ImplementationA : Implementation
{
    virtual operationA(){...}
    virtual operationB(){...}
    virtual operationC(){...}
}

// 实现B
class ImplementationB : Implementation
{
    virtual operationA(){...}
    virtual operationB(){...}
    virtual operationC(){...}
}

template
class Bridge
{
public:
    // 使用不同的类型来生成不同实现的实例化类
    Implementation* body;
    
    void operationA()
    {
        body->operationA();
    }
    
    void operationB()
    {
        body->operationB();
    }   
}

动多态和静多态两者的优点
动多态:

  • 可以处理异类集合
  • 生成的代码比较小,只需要一个多态函数,而静多态会生成多个实例化函数

静多态:

  • 不需要和公共基类绑定,可以自由实现处理类型

  • 代码运行效率更高,直接调用函数,虚函数间接调用;比虚函数拥有更多内联的机会

  • 参考:C++:模板类中如何调用其模板基类中的函数,C++模板二段名字查找,C++模板:名称查找,C++模板:静多态

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1001488.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号