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

1.模板实现的virtual多态问题

#include <iostream>
#include <string>
#include <cstdio>
#include <vector>

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

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

class Derived : public Base2<double> {
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<T>::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 <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <regex>
#include <type_traits>
#include <fstream>      // std::ofstream
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <typeinfo>
#include <cxxabi.h> //使用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<<typeid(y.f()).name()<<endl;
    cout<<abi::__cxa_demangle(typeid(y.f()).name(),0,0,0 )<<endl;//ubuntu上必须这么用
	return 0;
}
  • 测试:
    在这里插入图片描述

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

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <regex>
#include <type_traits>
#include <fstream>      // std::ofstream
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <typeinfo>
#include <cxxabi.h> //使用abi


using namespace std;


template<class T> class X {
public:
	typedef int E;
};
 
typedef double E;
 
template<class T> class Y : public X<T> {
public:
	E f() {}
};
int main(){
 
	Y<char> y;
	cout<<typeid(y.f()).name()<<endl;
     cout<<abi::__cxa_demangle(typeid(y.f()).name(),0,0,0 )<<endl;//ubuntu上必须这么用
	return 0;
}
  • 测试:
    在这里插入图片描述

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

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <regex>
#include <type_traits>
#include <fstream>      // std::ofstream
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <typeinfo>
#include <cxxabi.h> //使用abi


using namespace std;

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


int main(){
 
	Y<char> y;
     cout<<abi::__cxa_demangle(typeid(y.f()).name(),0,0,0 )<<endl;//ubuntu上必须这么用
     y.f();
	return 0;
}
  • 测试:
    (1)第一种方式调用了全局的g(),
    (2)第二种方式调用了基类的g(),
    (3)原因在于当编译器遇到第一种方式的g()时,发现它没有表现出要依赖于模板参数,所以认为它不依赖于模板参数,因而在第一阶段就进行了解析,此时X尚未解析,所以g()是全局的g();
    通过第二种方式调用时,用this指针指出g()是依赖于当前对象的,**也就依赖于模板参数,因而会在第二阶段解析,**那时基类也会先于Y进行了解析,所以this–>g()调用了基类的g()。
    在这里插入图片描述

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

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

3.错误解法与正确解法

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

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

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

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

正确解法2:使用this调用

  • 另一种解法是显式使用this,这样也可以将Method变成有依赖名字:
template <typename T>
class Base2: public Base<T> {
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<BaseType*> list) 
{
    ...
}


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

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

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

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

// 不可以处理异类集合,因为要在编译期间确定,vector只能为一个类型的集合
template<typename BaseType>
void DoActions(std::vector<BaseType*> 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<typename Implementation>
class Bridge
{
public:
    // 使用不同的类型来生成不同实现的实例化类
    Implementation* body;
    
    void operationA()
    {
        body->operationA();
    }
    
    void operationB()
    {
        body->operationB();
    }   
}

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

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

静多态:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢打篮球的普通人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值