effective c++ item40-44

本文讨论了C++中的多重继承可能引发的歧义问题和解决方案,如虚拟继承。同时,介绍了编译期多态的概念,通过模板实现编译期的接口检查。此外,解释了typename在处理嵌套从属名称时的关键作用,并给出了模板基类中访问特定名称的策略。最后,提到了代码膨胀问题及其解决方法,以及模板特化的概念和应用场景。
摘要由CSDN通过智能技术生成

item40:谨慎的使用多重继承

多重继承带来的符号的歧义性

类AB都是C的基类,并且都含有同一个成员,此时对该成员的访问具有歧义性。
在这里插入图片描述
消除歧义性

#include <iostream>
#include <vector>
using namespace std;
class A {
public:
  void f() { cout << "A" << endl; }
};
class B {
private:
  void f();
};
class C : public A, public B {};
int main() {
  C c;
  c.A::f();
}
钻石形多重继承

在这里插入图片描述
on the one hand, it inherits a copy from each of its base classes, so that suggests that IOFile should have two fileName data members. On the other hand, simple logic says that an IOFile object has only one file name, so the fileName field it inherits through its two base classes should not be replicated.

virtual虚继承

解决了多重继承的问题
在标准库中,basic istream和basic ostream库都是虚继承自basic ios的类,而basic iostream又继承自basic istream和basic ostream。

virtual继承的成本

objects created from classes using virtual inheritance are generally larger than they whould be without virtual inheritance. Access to data members in virtual base classes is also slower than to those in non-virtual base classes. The details vary from compiler to compiler, but the basic thrust is clear: virtual inheritance costs.

It costs in other ways, too. The rules governing the initialization of virtual base classes are more complicated and less intuitive than are those for non-virtual bases.

虚基类的初始化问题
class A{
public: 
	A(A* a);
};

class B:virtual public A{
public: 
	B(B* a):A(a){};
};

class C{
public: 
	C(C* a):A(a){};
};

class D:public B, public C{
public: 
	D(D* a):B(a), C(a){};	// compile error
	D(D* a):A(a), B(a), C(a){};		// correct
};

class E:public D{
public: 
	E(E* a):D(a){};			// compile error
	E(E* a):A(a), E(a){};		// correct
};

虚继承成本高,要维护基类

使用多重继承

item41:了解隐式接口和编译期多态

显示接口和运行期多态
class Widget{
public:
	virtual std::size_t size() const;
	virtual void normalize();
	void swap(Widget& other);
};

// 如果此处传入的是子类,则下面执行的函数是子类还是父类要在运行期做判断
void doProcessing(Widget& w){		
	if(w.size() > 10 && w != someWidget){
		Widget temp(w);
		temp.normalize();			// 显示的接口要求
		temp.swap(w);
	}
}

使用虚函数可以实现运行期的多态。

隐式接口和编译期多态
template<typename T>
void doProcessing(T& w){
	if(w.size() > 10 && w != someWidget){
		T temp(w);
		temp.normalize();
		temp.swap(w);
	}
}

在编译期做类型推导,得到T的类型。实现编译期多态
不论T是什么类型,其中必须要有size、normalize、swap、!=等接口。

item42:理解typename的双重含义

template<class T> class Widget;
template<typename T> class Widget;

class不能替代typename,但是typename可以替代class

template<typename C>
void print2nd(const C &constainer){		// C被称为从属名称
	if(constainer.size() > 2){
		// 这一行将会报错!!!!原因在下面
		C::const_iterator iter(constainer.cbegin());	// C::const_iterator被称为嵌套从属名称
		++ iter;
		std::cout << *iter << std::endl;
	}
}
int main(){
	std::vector<int> v{1,2,3,4};
	print2nd(v);
	return 0;
}

上面出错的语句原因不太直观,可以参考如下语句:

C::const_iterator * x;

编译器可能会以为在C中有名为const_iterator的整型成员,然后✖️x,解决方法为在嵌套从属名称前加typename:

typename C::const_iterator * x;		// 告知编译器后面是一个类型

typename使用规则也有例外:

  • 在以下这种情况下不需要加type name
template<typename T>
class Derived:public Base<T>::Nested{		// base class list:typename not allowed
public:
	// base class indetifier in mem.init.list:typename not allowed
	explicit Derived(int x):Base<T>::Nested(x){	
		// use of nested dependent type name:typename
		typename Base<T>::Nested temp;
	}
};

另外,还可以使用typedef或者using来简化编程:

template<typename IterT>
void workWithIterator(IterT iter){
	typename std::iterator_traits<IterT>::value_type temp(*iter);
}
// use typedef
template<typename IterT>
void workWithIterator(IterT iter){
	typedef typename std::iterator_traits<IterT>::value_type value_type;
	value_type temp(*iter);
}

item43:如何访问模版基类内的名称

在这里插入图片描述

#include <iostream>
using namespace std;

class A {
public:
  void fun1() { cout << "A : fun1" << endl; }
  void fun2() { cout << "A : fun2" << endl; }
};
class B {
public:
  void fun1() { cout << "B : fun1" << endl; }
  void fun2() { cout << "B : fun2" << endl; }
};
class C {
public:
  void fun2() { cout << "C : fun2" << endl; }
};

template <typename T>
class Father {
public:
  void first() {
    T t;
    t.fun1();
  }
  void second() {
    T t;
    t.fun2();
  }
};

template <>
class Father{
public:
	void first(){
		C c;
		c.fun3();
	}
}

template <typename T> class Son : public Father<T> {
public:
  void first_son() { first(); }
};

int main() {}

无法通过编译,因为可能存在特化版本,即可能存在类中没有func1,如类C。
模版特化的相关知识见
解决方法如下:

  • 使用this,指明向基类中寻找该函数
    在这里插入图片描述
  • 使用using
    在这里插入图片描述
  • 直接使用类型参数限定(最不推荐,影响多态性)
    在这里插入图片描述

item44:将与参数无关的代码抽离templates

代码膨胀

如下例:

#include <cstddef>
#include <iostream>
using namespace std;

template<typename T, std::size_t n>
class SquareMatrix{
public:
  void invert();
};

int main(){
  SquareMatrix<double, 5> s1;
  SquareMatrix<double, 10> s2;
}

在main函数中定义了两个变量,s1和s2,两个变量只有size不一样,而模版类会实例化出两份不同的代码。这就是代码膨胀

解决办法1

使用另一个类作为invert函数的实现类

#include <cstddef>
#include <iostream>
using namespace std;

template<typename T>
class SquareMatrixBase{
public:
  void invert(std::size_t n);
};

template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>{
private:
  using SquareMatrixBase<T>::invert;
public:
  void invert(){
    invert(n);
  }
};

int main(){
  SquareMatrix<double, 5> s1;
  SquareMatrix<double, 10> s2;
}

附录:模版特化

模板特化的原因:模板并非对任何模板实参都合适、都能实例化,某些情况下,通用模板的定义对特定类型不合适,可能会编译失败,或者得不到正确的结果。因此,当不希望使用模板版本时,可以定义类或者函数模板的一个特例化版本。
模板特化:模板参数在某种特定类型下的具体实现。分为函数模板特化和类模板特化

  • 函数模板特化:将函数模板中的全部类型进行特例化,称为函数模板特化。
  • 类模板特化:将类模板中的部分或全部类型进行特例化,称为类模板特化。

特化分为全特化和偏特化:

  • 全特化:模板中的模板参数全部特例化。
  • 偏特化:模板中的模板参数只确定了一部分,剩余部分需要在编译器编译时确定。

说明:要区分下函数重载与函数模板特化
定义函数模板的特化版本,本质上是接管了编译器的工作,为原函数模板定义了一个特殊实例,而不是函数重载,函数模板特化并不影响函数匹配。

#include <iostream>
#include <cstring>

using namespace std;
//函数模板
template <class T>
bool compare(T t1, T t2)
{
    cout << "通用版本:";
    return t1 == t2;
}

template <> //函数模板特化
bool compare(char *t1, char *t2)
{
    cout << "特化版本:";
    return strcmp(t1, t2) == 0;
}

int main(int argc, char *argv[])
{
    char arr1[] = "hello";
    char arr2[] = "abc";
    cout << compare(123, 123) << endl;
    cout << compare(arr1, arr2) << endl;

    return 0;
}
/*
运行结果:
通用版本:1
特化版本:0
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值