您的位置:首页 > 编程语言 > C语言/C++

《effective c++》学习笔记(七)

2017-09-10 21:56 239 查看

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

查看下面这段代码:

template<typename T>
bool foo(const T& lhs, const T& rhs) {
if (lhs.bar() && rhs.bar()) {
return true;
}
return false;
}


当调用
foo()
时,会根据参数的类型来实例化出函数,也就是我
foo()
这个语句,可以调用不同的函数,这就是编译期多态。

而隐式接口则是规定调用
foo
的参数必须要有bar这个成员函数,否则就会编译错误(实例化出函数后,编译错误)

classes和template都支持接口和多态

对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期

对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期

了解typename双重含义

对于一个template声明

template<typename T>
class Foo{
};




template<class T>
class Foo{
};


是一模一样的,typename和class没有任何区别

但考虑下面这份代码:

template<typename T>
class Foo{
public:
typedef T value_type;
};

template<typename T>
void bar() {
Foo<T>::value_type a;
}

int main() {
bar<int>();
return 0;
}


原因是template内出现的名称如果相依于某个template参数,称之为从属名称。如果从属名称在class内呈嵌套状,称之为嵌套从属名称。而嵌套从属名称,会被编译器认为不是一个类型,而是其他东西(比如一个static成员变量),这个时候需要在前面加一个typename:

template<typename T>
class Foo{
public:
typedef T value_type;
};

template<typename T>
void bar() {
typename Foo<T>::value_type a;
}

int main() {
bar<int>();
return 0;
}


但这里有一个例外就是,typename不可以出现在base classes list内的嵌套从属名称类型名称之前,也不可以在member initialization list中作为base class修饰符

声明template参数时,前缀关键字class和typename可互换

请使用关键字typename标识嵌套从属类型名称:但不是在base class lists(基类列)或member initialization list内以它作为base class修饰符

学习处理模板化基类的名称

考虑下面这份代码:

template<typename T>
class Base {
public:
void foo() { }
};

template<typename T>
class Derived : Base<T> {
public:
void bar() {
foo();
}
};


一切看起来都很正常,但编译器会提示找不到foo这个函数的声明。。。

原因是因为编译器不会去找模板基类的名称,有三种办法可以解决这个问题:

添加this指针
this->foo();


显示调用
Base<T>::foo();


使用using
using Base<T>::foo;


-

可在derived class templates内通过”this->”指涉base classes template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成

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

考虑下面这份代码:

template<typename T, size_t n>
class Matrix{
public:
Matrix() : _data(nullptr) {  }
private:
shared_ptr<T> _data;
};


当我定义了许多个不同的Matrix时

Matrix<int, 1> m1;
Matrix<int, 2> m2;
Matrix<int, 3> m3;


这份class就会实例化出3份class,最后程序的代码段会很长,充斥着重复代码,实际上完全可以把
size_t n
当作一个成员变量来使用

template<typename T>
class Matrix{
public:
Matrix(size_t size) : _data(nullptr), _size(size)  {  }
private:
shared_ptr<T> _data;
size_t        _size;
};


Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数相依关系

因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数

因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码

需要类型转换时请为模板定义非成员函数

考虑下面这份代码

#include <bits/stdc++.h>
using namespace std;

template<typename T>
class Foo{
public:
Foo() = default;
Foo(int x) {  }
};

template<typename T>
const Foo<T> operator*(const Foo<T> &lhs, const Foo<T> &rhs) {
Foo<T> ret;
return ret;
}

int main() {
Foo<int> f;
2 * f;
return 0;
}


我们的预期是在执行
2 * f
时,2可以隐式转为Foo,然后和f相乘。但实际上这份代码不能通过编译,原因是因为编译器根据2推断出T是int类型,而int不能接受一个Foo。如果写成
f * 2
,结果也是一样的,因为参数一个是Foo,一个是int,无法推断出来T是什么类型。

解决办法是,要让执行operator*时,函数已经被实例化了。做法是我们把operator*放在class内并声明一个friend就可以了

#include <bits/stdc++.h>
using namespace std;

template<typename T>
class Foo{
friend const Foo operator*(const Foo &lhs, const Foo &rhs) {
Foo ret;
return ret;
}
public:
Foo() = default;
Foo(int x) {  }
};

int main() {
Foo<int> f;
2 * f;
return 0;
}


这样的话当class被实例化时,这个函数也就被实例化了,当调用operator*时就会自动调这个friend函数。

最后还有一个问题就是class内的函数隐式inline,如果想要避免代码膨胀,可以使用这个class内部的friend函数调用一个non-member operator*辅助函数即可。

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”

请使用traits classes表现类型信息

对于STL数据结构和算法,你可以使用五种迭代器。下面简要说明了这五种类型:

Input iterators 提供对数据的只读访问。

Output iterators 提供对数据的只写访问

Forward iterators 提供读写操作,并能向前推进迭代器。

Bidirectional iterators提供读写操作,并能向前和向后操作。

Random access iterators提供读写操作,并能在数据中随机移动。

以std::advance为例,对于Random迭代器,我们可以直接进行
+=
操作,对于其他类型,只能一步一步的
++
操作,而在编译时获得模板类型可以使用traits

template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::random_access_iterator_tag ){
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::bidirectional_iterator_tag){
if( d>=0 ) { while (d--) ++iter; }
else { while( d++ ) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::input_iterator_tag){
if( d<0 )
throw std::out_of_range("Nagative Distance");
while (d--) ++iter;
}

template<typename IterT, typename DistT>
void advance( IterT& iter, DistT d ){
doAdvance( iter, d,
typename std::iterator_traits<IterT>::iterator_category()
);
}


Traits classes使得“在编译期可用。它们以templates和”templates特化“来完成

整合重载技术后,traits classes有可能在编译期对类型执行if…else测试

认识template元编程

所谓模板元编程是以C++写成、执行与C++编译器内的程序。一旦TMP程序结束执行,其输出,也就是从templates具现出来的若干C++源码,便会一如既往地被编译。

由于执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。

下面是一个计算N!的TMP程序:

template<size_t n>
struct Fact {
enum {
value = Fact<n - 1>::value * n
};
};

template<>
struct Fact<0> {
enum {
value = 1
};
};

int main(int argc, char const* argv[]) {
cout << Fact<10>::value << endl;        //3628800
return 0;
}


当程序编译成功的那一刻,10!已经被计算出来了,多么神奇的一件事情!

Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率

TMP可将用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++