#! https://zhuanlan.zhihu.com/p/537589122
class GamePlayer{
private:
enum { NumTurns = 5};
// static const int NumTurns = 5;
int scores[NumTurns];
};
class Test{
public:
// 空类自带
Test() = default;
Test(const Test& t) = default;
Test& operator=(const Test& t)= default;
~Test() = default;
// 移动(右值引用),需要清除输入资源
Test(const Test&& t) noexcept{}
Test& operator=(const Test&& t) noexcept{}
};
class Uncopyable{
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale : private Uncopyable{};
Base* ptr = New Derived(); delete ptr;
中只会调用base的析构,而derived类很可能没有被销毁。virtural ~Test()=0
) = 抽象类 = 不能单独实例化 = 派生类必须要定义该函数(如果基类未定义)class Widget{
Widget& operator=(const Widget& rhs){
...
return *this;
}
}
class BitMap{};
class Widget{
// 1.不安全实现版本,不具备异常安全性,
// 如果new操作异常(内存不足或者拷贝构造函数异常),pb会指向一个删除了的地址。
Widget& operator=(const Widget& rhs){
if(this == &rhs) return *this; // 证同测试
delete pb;
pb = new BitMap(*rhs.pb);
return *this;
}
// 2.异常安全性,非最高效,但行得通
// 如果很关心效率,可以加入证同测试
Widget& operator=(const Widget& rhs){
BitMap* pOring = pb;
pb = new BitMap(*rhs.pb);
delete pOring;
return *this;
}
// 3.copy and swap技术。异常安全+自我复制安全。一般用这个
Widget& operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
// 4.传值
Widget& operator=(Widget rhs){
swap(rhs);
return *this;
}
void swap(Widget& rhs);// 详见第29条
private:
BitMap* pb; //从heap分配的指针
};
如下第二个有风险的函数调用,在调用之前,编译器必须创建代码:
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);
// 错误,不支持隐式转换
processWidget(new Widget, priority());
// 会有风险
processWidget(std::shared_ptr<Widget>(new Widget), priority());
// OK,new指针独立语句
std::shared_ptr<Widget> pw(new Widget)
processWidget(pw, priority());
class Date{
public:
// 不行:1.容易输入错误次序,2.容易传递无效月份或天数
Date(int month, int day, int year);
// 可以:使用外覆类型(wrapper types)来区分年月日(构造函数设为显示)
Date(const Month& m, const Day& d, const Year& y);
};
// 不行:容易内存泄露
Investment* createInvestment();
// 可以:使用智能指针
std::shared_ptr<Investment> createInvestment();
注意class的重载、操作符、控制内存的分配和归还、传参、继承、类型转换、定义对象的初始化和终结等的设计,和默认类型一样设计。
int& func(){
// 栈:编译就会产生warning:reference to local variable returned [-Wreturn-local-addr]
int a = 99;
return a;
// 堆:能用,但是没有delete,容易导致内存泄漏
int* a = new int(99);
return *a;
}
clearEverything
比clearBrowser
的封装性低。class WebBrowser {
public:
void clearCache() {}
void clearHistory() {}
void removeCookies() {}
// 1. 没2封装性好,用2
void clearEverything() {
clearCache();
clearHistory();
removeCookies();
}
};
// 2.
void clearBrowser(WebBrowser& wb){
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
#include "webbrowser.h"
namespace WebBrowserStuff {
class WebBrowser {
public:
void clearCache() {}
void clearHistory() {}
void removeCookies() {}
};
// 核心机能等
void clearBrowser(WebBrowser& wb) {
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
} // namespace WebBrowserStuff
#include "webbrowserbookmarks.h"
namespace WebBrowserStuff {
// 与书签相关的便利函数(非成员非友元)
} // namespace WebBrowserStuff
res = 2 * one_half
,相当于2.operator*(one_half)
,中的2没有相应的class,也就没有operator* 成员函数。同事也没有非成员函数满足operator*(2, one_half)
operator*(2, one_half)
,其中2进行了隐式转换Rational(2)
// 有理数
class Rational {
public:
// 允许隐式转换
Rational(int numerator = 0, int denominator = 1)
: _numerator(numerator), _denominator(denominator) {}
// 1.
// const Rational operator*(const Rational& rhs) {
// Rational res(this->numerator() * rhs.numerator(),
// this->denominator() * rhs.denominator());
// return res;
// }
int numerator() const { return _numerator; }
int denominator() const { return _denominator; }
private:
// 分子
int _numerator;
// 分母
int _denominator;
};
// 2.
const Rational operator*(const Rational& lhs, const Rational& rhs) {
Rational res(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
return res;
}
int main() {
Rational one_half(1, 2);
Rational res;
res = one_half * 2; // 1和2都可以
res = 2 * one_half; // 只有2可以
return 0;
}
namespace std{
template<typename T>
swap(T a, T b){
T tmp(a);
a = b;
b = tmp;
}
}
// a复制到tmp,b复制到a,tmp复制到b
简单类型不用考虑,主要是指针类型,常见表现形式是pipml
手法(pointer to implementation,见条款31)
下面Widget
类使用自带的swap时,复制三遍Widget,还复制三个WidgetImpl对象,缺乏效率。而实际只需要交换指针。因此设计如下,跟STL容器有一致性
class WidgetImpl {
private:
// 可能数据很多,复制时间很长
int a, b, c;
std::vector<double> vec;
};
class Widget {
public:
Widget(const Widget& rhs) {}
Widget& operator=(const Widget& rhs) {
*pImpl = *(rhs.pImpl);
return *this;
}
void swap(Widget& other) { std::swap(pImpl, other.pImpl); }
private:
WidgetImpl* pImpl;
};
// 全特化模板
namespace std {
template <>
void swap<Widget>(Widget& a, Widget& b) {
a.swap(b);
}
} // namespace std
偏特化
类模板,不允许偏特化
函数模板。尤其是管理规则比较特殊的std内的模板,只支持全特化
template<typename T>
class WidgetImpl {
private:
std::vector<T> vec;
};
template<typename T>
class Widget {
...
private:
WidgetImpl<T>* pImpl;
};
namespace std {
//error:此声明中不允许使用显式模板参数列表
template <typename T>
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
// 重载版本,但是也不合法,std标准委员会禁止膨胀已经声明好的东西,能编译运行,但它的行为没有明确定义
// 如果希望你的软件有可预期的行为,请不要添加任何新的东西到std里头。
template <typename T>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
}
namespace WidgetStuff{
template <typename T>
class WidgetImpl;
template <typename T>
class Widget;
// 非成员函数,不在std内
template <typename T>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
}
std::string encryptPassword(const std::string& password){
// 过早,如果丢出异常,造成不必要的encrypted构造和析构
// std::string encrypted;
if(password.length() < 8){
throw logic_error("Password is too short");
}
// 可以,且拷贝构造初始化了
std::string encrypted(password);
// 加密函数
encrypt(encrypted);
return encrypted;
}
// 这种好,尤其是n很大的时候,1构造 + 1析构 + n赋值
Widget w;
for(int i = 0; i < n; ++i){
w = 某个值;
}
// n构造 + n析构。如果Widget很大,构造相当耗时
for(int i = 0; i < n; ++i){
Widget w(某个值);
}
handles指包括引用、指针、迭代器。
返回指针指向某个成员函数
struct Point {
int x = 0;
int y = 0;
};
class Test {
public:
// 错误,虽然能通过编译。声明了const函数,返回了引用指向私有成员,从而调用者可以更改内部数据
int& GetX() const { return _pt->x; }
// 可以,但也有风险,见下
const int& GetX() const { return _pt->x; }
private:
std::shared_ptr<Point> _pt;
};
struct Point {
int x = 99;
int y = 66;
};
class Test {
public:
Test() {
cout << "Test()" << endl;
_pt = new Point();
}
~Test() {
cout << "~Test()" << endl;
delete _pt;
}
const int &GetX() const { return _pt->x; }
private:
// 假设这里是用的普通指针
Point *_pt;
};
int main() {
const int *ptr = nullptr;
{
// Test临时对象,出作用域后会析构,那么x指针也会被释放
Test t;
auto x = &(t.GetX());
cout << "1 dir:" << x << " value:" << *x << endl;
ptr = x;
cout << "2 dir:" << ptr << " value:" << *ptr << endl;
}
// 能编译运行,但运行时该指针指向一个不存在的对象,造成指针空悬、虚吊(dangling)
// 共享指针没有不会出现该问题
cout << "3 dir:" << ptr << " value:" << *ptr << endl;
return 0;
}
//1 dir:0x21987721a70 value:99
//2 dir:0x21987721a70 value:99
//~Test()
//3 dir:0x21987721a70 value:-2022565648
class Image{
public:
Image(const std::istream& img_src){}
};
class PrettyMenu{
public:
// 这个函数很糟糕,异常安全的两个条件都没有满足
void ChangeBackground(std::istream& img_src){
mtx.lock();
delete bg_image;
++image_changes;
bg_image = new Image(img_src); // 一旦new失败,不会unlock(资源泄漏),且指针被delete,changes累加了但是图像没有被修改(数据破坏)。
mtx.unlock();
}
// 修改后,可解决资源泄漏问题,数据破坏没解决
void ChangeBackground2(std::istream& img_src){
std::lock_guard<std::mutex> auto_lock(mtx);
delete bg_image;
++image_changes;
bg_image = new Image(img_src);
}
private:
std::mutex mtx;
Image* bg_image;
int image_changes;
};
1基本承诺
class Image{
public:
explicit Image(const std::istream& img_src){}
};
class PrettyMenu{
public:
void ChangeBackground(std::istream& img_src){
std::lock_guard<std::mutex> auto_lock(mtx);
bg_image.reset(new Image(img_src));
++image_changes;
}
private:
std::mutex mtx;
shared_ptr<Image> bg_image;
int image_changes;
};
2强烈保证
class Image{
public:
explicit Image(const std::istream& img_src){}
};
struct PMImpl{
shared_ptr bg_image;
int image_changes;
};
class PrettyMenu{
public:
void ChangeBackground(std::istream& img_src){
std::lock_guard auto_lock(mtx);
shared_ptr p_new(new PMImpl(*p_impl));
p_new->bg_image.reset(new Image(img_src));
++p_new->image_changes;
std::swap(p_impl, p_new);
}
private:
std::mutex mtx;
shared_ptr p_impl;
};
接口和实现的解耦,降低文件间的编译依存性。
个人觉得这节很重要,值得仔细看看。有时候大工程因为改了一点小地方,却发现要重新编译整个工程浪费很长时间。我们要做的就是降低文件间的编译依存性,解决这个问题。
假设三个类A,B,C,C依赖B,B依赖A,六个类文件和一个main文件。通常的做法:
// a.h
#pargma once
class A{
public:
A();
};
// a.cpp
#include "a.h"
A::A(){}
/***************/
// b.h
#pargma once
#include "a.h"
class B{
public:
B();
A _a;
};
// b.cpp
#include "b.h"
B::B(){}
/***************/
// c.h
#pargma once
#include "b.h"
class C{
public:
C();
B _b;
};
// c.cpp
#include "c.h"
C::C(){}
/***************/
//mian.cpp
#include "c.h"
int main(){
C c;
return 0;
}
接口是指.h
,实现是指.cpp
。c++编译分两步编译.o文件
和链接.o成执行文件(exe/其他)
。接口和实现改变都需要重新编译.o,但两者也有区别,最好只修改实现。
一般来说,我们改cpp也就是实现的地方,只会需要重新编译该文件的.o,而依赖该文件的接口也就是.h没变,所以依赖的文件是不需要重新编译的。
也就是说,修改a.cpp,只需要重新编译a.cpp.o
和链接成执行文件。这也是类设计的时候需要接口和实现分离两个文件的原因之一!
但是当我们修改接口a.h 时,需要重新编译a.cpp.o
、b.cpp.o
、c.cpp.o
、main.cpp.o
和链接成执行文件。这就会导致浪费时间编译整个工程!
编译依存最小化有两种方法:Handle classes
和Interface classes
前置声明+指针
对于上面的示例可以更改为如下示例。主要改动有两点
一是在有依赖的接口前面去掉依赖的头文件,加上依赖类前置声明,在实现里加入依赖头文件:如b.h去掉了#include "a.h"
,前置声明了class A
,在b.cpp里面加入了#include "a.h"
二是在有依赖的接口里面,使用指针声明成员变量:如B类里面使用了A* _a
声明成员。因为A只有声明,此时这个地方没有定义,编译器不知道_a具体分配多少内存,但是使用指针就可以。“将对象实现细目隐藏于一个指针背后的游戏”,这也是该方法的巧妙之处!
此时我们修改a.h时,需要重新编译a.cpp.o
、b.cpp.o
和链接成执行文件,b.cpp.o
是因为b.cpp里面包含了A的接口,A接口变了,所以需要重新编译,但是B的接口没变,所以后面的两个都不需要重新编译!
// a.h
#pargma once
class A{
public:
A();
};
// a.cpp
#include "a.h"
A::A(){}
/***************/
// b.h
#pargma once
class A; //前置声明
class B{
public:
B();
A* _a;
};
// b.cpp
#include "b.h"
#include "a.h" //cpp中加入头文件
B::B(){}
/***************/
// c.h
#pargma once
class B;
class C{
public:
C();
B* _b;
};
// c.cpp
#include "c.h"
#include "b.h"
C::C(){}
/***************/
//mian.cpp
#include "c.h"
int main(){
C c;
return 0;
}
书上的示例如下,设计原理差不多。对外是person类,而具体是由personimpl类来实现,这种设计就是pimpl idiom(pointer to implementation)
,一般来说,person不会改动,主要是personimpl来修改,封装的很严实,也是真正的接口与实现分离
。
分离的关键就在于以声明的依存性
替换定义的依存性
person.h
#ifndef PERSON_H
#define PERSON_H
#include
class PersonImpl;
class Person
{
public:
Person(const std::string& name);
std::string name() const;
private:
std::shared_ptr<PersonImpl> pImpl;
};
#endif // PERSON_H
person.cpp
#include "person.h"
#include "personimpl.h"
Person::Person(const std::string& name) : pImpl(new PersonImpl(name)){}
std::string Person::name() const{
return pImpl->name();
}
personimpl.h
#ifndef PERSONIMPL_H
#define PERSONIMPL_H
#include
class PersonImpl
{
public:
PersonImpl(const std::string& name);
std::string name() const;
private:
std::string _name;
};
#endif // PERSONIMPL_H
personimpl.cpp
#include "personimpl.h"
PersonImpl::PersonImpl(const std::string& name):_name(name){}
std::string PersonImpl::name() const{
return _name;
}
抽象基类+继承+指针
person.h
#ifndef PERSON_H
#define PERSON_H
#include
class Person
{
public:
virtual ~Person() = default;
static std::shared_ptr<Person> create();
virtual std::string name() const = 0;
};
#endif // PERSON_H
person.cpp
#include "person.h"
#include "personimpl.h"
std::shared_ptr<Person> Person::create(){
return std::shared_ptr<Person>(new PersonImpl);
}
personimpl.h
#ifndef PERSONIMPL_H
#define PERSONIMPL_H
#include
#include "person.h"
class PersonImpl : public Person
{
public:
PersonImpl();
~PersonImpl() = default;
std::string name() const;
private:
std::string _name;
};
#endif // PERSONIMPL_H
personimpl.cpp
#include "personimpl.h"
PersonImpl::PersonImpl():_name("admin"){}
std::string PersonImpl::name() const{
return _name;
}
main.cpp
#include
#include "person.h"
using namespace std;
int main()
{
std::shared_ptr<Person> per(Person::create());
cout << per->name() << endl;
return 0;
}
class Base{
virtual ~Base(){}
};
class Derived : public Base{
~Derived(){}
}
has a
和is-implemented-in-terms-of
(根据某物实现出)using声明式
和私有继承+转交函数
解决。class Base{
virtura ~Base(){}
virtual void f1();
virtual void f1(int);
void f2();
void f2(float);
};
class Derived : public Base{
~Derived(){}
// 遮掩了base的所有f1 f2函数
virtual void f1();
void f2();
}
Derived d;
d.f1(); //Derived::f1
d.f2(); //Derived::f2
d.f1(1); //error,被遮掩
d.f2(1.3f); //error,被遮掩
using声明式
class Derived : public Base{
~Derived(){}
using Base::f2;
using Base::f1;
virtual void f1();
void f2();
}
d.f1(1); //Base::f1(int)
d.f2(1.3f); //Base::f2(float)
私有继承+转交函数
class Derived : private Base{
~Derived(){}
virtual void f1(){
Base::f1();
}
...
}
函数接口
和函数实现
virtual void f() = 0;
,派生类只继承函数接口,且需要强制性重新实现virtual void f();
,派生类继承函数接口和默认函数实现NVI + Strategy
使用非virtual 接口手法实现Template Method
模式
Template Method
模式之一:使用普通成员函数调用私有虚函数,non-virtual interface(NVI)
手法
派生类需要重新定义虚函数时,可以设为protected
class GameCharacter{
public:
// 虚函数的wrapper
int healthValue() const{
return doHealthValue();
}
// private:
protected:
virtual int doHealthValue() const{
return health_value;
}
int health_value;
};
函数指针
实现Strategy
模式:就是构造函数使用函数指针传递,派生类继承基类,在初始化的时候可以使用不同的健康计算函数class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc) { return 1; }
// 两种函数指针设计都可以,第二种是c++11新特性
// typedef int (*HealthCalCFunc)(const GameCharacter&);
typedef std::function<int(const GameCharacter&)> HealthCalCFunc;
class GameCharacter {
public:
explicit GameCharacter(HealthCalCFunc hcf = defaultHealthCalc)
: healthFuncPtr(hcf) {}
virtual ~GameCharacter() {}
int healthValue() const { return healthFuncPtr(*this); }
private:
HealthCalCFunc healthFuncPtr;
};
class EvilBadGuy : public GameCharacter {
public:
explicit EvilBadGuy(HealthCalCFunc hcf = defaultHealthCalc)
: GameCharacter(hcf) {}
};
int main() {
GameCharacter gc(defaultHealthCalc);
// 派生类可以传递其他的健康函数
EvilBadGuy ebg1(defaultHealthCalc);
cout << ebg1.healthValue() << endl;
return 0;
}
Strategy
模式是将健康计算函数设计成一个基类,不同的健康计算函数派生。然后用类指针替换任务里的函数指针,本质是一样的。设计模式里面的内容。class Base {
public:
virtual ~Base() {}
void f() { cout << "base f()" << endl; }
};
class Derived : public Base {
public:
// 遮掩了Base::f(),所以不要重新定义
void f() { cout << "Derived f()" << endl; }
};
int main() {
Derived x;
Base* pb = &x;
Derived* pd = &x;
pb->f(); // base f()
pd->f(); // Derived f()
return 0;
}
class Base {
public:
virtual ~Base() {}
virtual void f(int num = 1) { cout << "base : " << num << endl; }
};
class Derived1 : public Base {
public:
// 赋予不同的默认值,不行
virtual void f(int num = 2) { cout << "Derived1 : " << num << endl; }
};
class Derived2 : public Base {
public:
// 静态绑定时需要传入参数,动态绑定时这个函数就会从基类中继承默认值
virtual void f(int num) { cout << "Derived2 : " << num << endl; }
};
int main() {
Derived1 d;
Base* pb1 = new Derived1();
Base* pb2 = new Derived2();
// 没问题,静态类型是Derived1,所以默认参数也是2
d.f(); // Derived1 : 2
// 大问题,静态类型是Base*,虽然调用了派生类的函数,但是默认参数却是使用了静态绑定的1
pb1->f(); // Derived1 : 1
// 没问题,继承静态绑定的1
pb2->f(); // Derived2 : 1
delete pb1;
delete pb2;
return 0;
}
has-a
或根据某物实现出
is a
公有继承(私有继承不是)has a
复合(应用域:仅类成员)is-implemented-in-terms-of
复合(实现域:由类成员实现该类的内容,比如用一个链表成员实现平衡二叉树)、私有继承私有继承的派生类
转换为基类
,继承的成员全部变成私有属性class Person {
public:
virtual ~Person() {}
virtual void f() const { cout << "person f()" << endl; }
};
class Student : private Person {
public:
virtual void f() const { cout << "Student f()" << endl; }
};
void doSomething(const Person& p) { p.f(); }
int main() {
Person p;
Student s;
doSomething(p);
// 私有继承编译报错,公有继承可以运行且多态
doSomething(s);
return 0;
}
is-implemented-in-terms-of
,比复合的级别低,但是当派生类需要访问基类的protected成员,或者重新定义virtual函数时,这么设计是合理的。class A {
public:
void checkout() { cout << "A" << endl; }
};
class B {
public:
void checkout() { cout << "B" << endl; }
};
class C : public A, public B {};
int main() {
C c;
// 编译错误 "C::checkout" 不明确
// c.checkout();
// 可以
c.A::checkout();
c.B::checkout();
return 0;
}
class File {
public:
string file_name;
};
class InputFile : public File {};
class OutputFile : public File {};
class IOFile : public InputFile, public OutputFile {};
int main() {
IOFile io;
// 编译错误 "IOFile::file_name" 不明确
io.file_name;
return 0;
}
// 继承了两份file_name,但实际IOFile对象只改有一个文件名称。解决方法就是virtual继承
// c++标准库中的basic_ios, basic_istream, basic_ostream, basic_iostream就是钻石型多重继承
class File {
public:
string file_name;
};
class InputFile : virtual public File {};
class OutputFile : virtual public File {};
class IOFile : public InputFile, public OutputFile {};
int main() {
IOFile io_file;
io_file.file_name;
return 0;
}
//
//
显示
,以函数签名为中心。多态通过virtual发生在运行期
隐式
, 奠基于有效表达式。多态通过模板具现化和函数重载解析发生于编译期
// T类型需要支持size函数,编译期时就回检查
template<typename T>
void doSomething(T& t){
t.size();
}
template<class T> f(T& t);
template<typename T> f(T& t);
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
// 模板类出现某个名称依赖于某个模板参数C,则是从属名称。从属名称在class内呈嵌套状,则是嵌套从属名称
// 1. 编译不过,C::const_iterator是嵌套从属名称。const_iterator无法确认是类型还是变量
// C::const_iterator iter(container.begin());
// 2. 可以。
typename C::const_iterator iter(container.begin());
++iter;
cout << *iter << endl;
}
}
template<typename C> // class和typename都可以
void f(const C& container, // 不允许使用typename
typename C::iterator iter) // 必须使用typename
{}
template<typename T>
class Base{
public:
Base() = default;
virtual ~Base() = default;
class Nested{
public:
explicit Nested(int x){}
Nested() = default;
};
};
template<typename T>
class Derived : public Base<T>::Nested{ // 基类列(base classes list)不允许typename
public:
explicit Derived(int x) : Base<T>::Nested(x){ // 成员初始化(member initialization list)不允许typename
typename Base<T>::Nested temp; // 嵌套从属名称必须使用typename
}
};
this
或者明确域
,否则基类模板的函数会被派生类覆盖遮掩。class CompanyA{
public:
void sendClearText(const string& msg){}
};
class CompanyB{
public:
void sendEncrypted(const string& msg){}
};
// 基类模板
template<typename T>
class MsgSender{
public:
void sendClear(const string& info){
T company;
company.sendClearText(info);
}
};
// 基类模板全特化
template<>
class MsgSender<CompanyB>{
public:
void sendSecret(const string& info){
CompanyB company;
company.sendEncrypted(info);
}
};
// 派生类模板
template<typename T>
class LoggingMsgSender: public MsgSender<T>{
public:
void SendClearMsg(const string& info){
// 1. 编译错误,进入templated base classes观察的行为失效
// 无法知道MsgSender是否有sendClear函数。尤其是存在特化版本MsgSender时,并没有该函数,
// sendClear(info);
// 2. 可以,加this, 假设成功继承。但是使用特化版本基类没有sendClear会编译错误,下同。
// this->sendClear(info);
// 3. 可以,明确指出域(或者使用函数外使用using MsgSender::sendClear)
MsgSender<T>::sendClear(info);
}
};
模板可以节省时间和避免代码重复,也可能会导致代码膨胀(似乎影响不大,见C++ 模板带来的代码膨胀有多少影响?)。进行共性与变性分析
,抽离无关的代码
以设计某动态尺寸的正方矩阵模板为例,该矩阵支持求逆矩阵运算。为了减少求逆运算的代码膨胀,设计一个基类并私有继承,如下:
template<typename T>
class SquareMatrixBase{
protected:
SquareMatrixBase(size_t n, T* p_mem) : size(n), p_data(p_mem){}
void invert(size_t matrix_size){}
private:
size_t size;
T* p_data; // 获取派生类矩阵数据的指针
};
// 私有继承,意味Base类只是个辅助类,不是is-a关系
// T是模板类型参数,n是非模板类型参数
template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T>{
public:
SquareMatrix() : SquareMatrixBase<T>(n, data){}
void invert(){
SquareMatrixBase<T>::invert(n);
}
private:
T data[n*n]; // 存储矩阵数据
};
上述设计的优点有:
因非类型模板参数
造成的代码膨胀,可以消除,用函数参数或者class成员变量替换非类型模板参数
,如上面n的设置
因类型模板参数
造成的代码膨胀,可以降低,用完全相同二进制表述的具现类型共享实现码。如上面invert函数的设计
class Base{
public:
virtual ~Base() = default;
};
class Derived : public Base{};
template<typename T>
class SmartPtr{
public:
explicit SmartPtr(T* ptr) : _ptr(ptr){} // 以原始指针初始化
// 成员模板,泛化拷贝构造函数:不声明explicit,因为派生类指针转基类指针是隐式转换,是合理的
template<typename U>
SmartPtr(const SmartPtr<U>& other){}
T* get(){return _ptr;}
private:
T* _ptr;
};
int main() {
Base* ptr = new Derived;
SmartPtr<Base> pt1 = SmartPtr<Derived>(new Derived);
return 0;
}
泛化拷贝构造
或泛化赋值操作
,正常的拷贝构造和赋值操作函数还是需要声明的。24
条的有理数例子进行模板化设计,为了支持模板的混合式算数运算,设计如下:template<typename T>
class Rational {
public:
Rational(const T& numerator = 0, const T& denominator = 1)
: _numerator(numerator), _denominator(denominator) {}
T numerator() const { return _numerator; }
T denominator() const { return _denominator; }
// 保证混合运算,定义在class内的非成员-友元函数
// 如果仅声明,定义在class外,能编译,不能链接,报错
friend Rational operator*(const Rational& lhs, const Rational& rhs){
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
private:
T _numerator;
T _denominator;
};
int main() {
Rational<int> one_half(1, 2);
Rational<int> result = one_half * 2;
return 0;
}
STL主要由容器、迭代器、算法等
的模板组成,也包括若干工具类模板,例如advance,用来将某个迭代器移动某个给定距离,但迭代器有5种,移动方式也不同:
以上五种分类,C++标准程序库分别提供了专属卷标结构(tag struct)加以确认
...\c++\bits\stl_iterator_base_types.h
/**
* @defgroup iterator_tags Iterator Tags
* These are empty types, used to distinguish different iterators. The
* distinction is not made by what they contain, but simply by what they
* are. Different underlying algorithms can then be used based on the
* different operations supported by different iterator types.
*/
///@{
/// Marking input iterators.
struct input_iterator_tag { };
/// Marking output iterators.
struct output_iterator_tag { };
/// Forward iterators support a superset of input iterator operations.
struct forward_iterator_tag : public input_iterator_tag { };
/// Bidirectional iterators support a superset of forward iterator
/// operations.
struct bidirectional_iterator_tag : public forward_iterator_tag { };
/// Random-access iterators support a superset of bidirectional
/// iterator operations.
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
traits允许在编译器取得某些类型信息,Traits不是c++关键字或一个定义好的构建,是一种技术,是c++程序员共同遵守的协议
,设计如下:template<typename IterT, typename DistT>
void MyAdvance(IterT &iter, DistT d) {
if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d;
} else {
if (d > 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
template<typename IterT, typename DistT>
void MyAdvance(IterT &iter, DistT d) {
if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d;
} else {
if (d > 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
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; }
}
// forward_iterator_tag公有继承自input_iterator_tag,所以该函数也能处理forward_iterator_tag
template<typename IterT, typename DistT>
void DoAdvance(IterT &iter, DistT d, std::input_iterator_tag){
if (d < 0) {
throw std::out_of_range("Negative distance");
}
while (d--) ++iter;
}
template<typename _InputIterator, typename _Distance>
inline _GLIBCXX17_CONSTEXPR void
advance(_InputIterator& __i, _Distance __n)
{
// concept requirements -- taken care of in __advance
typename iterator_traits<_InputIterator>::difference_type __d = __n;
std::__advance(__i, __d, std::__iterator_category(__i));
}
// 下面是五种迭代器的重载函数,截取了两种
template<typename _InputIterator, typename _Distance>
inline _GLIBCXX14_CONSTEXPR void
__advance(_InputIterator& __i, _Distance __n, input_iterator_tag)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_assert(__n >= 0);
while (__n--)
++__i;
}
template<typename _BidirectionalIterator, typename _Distance>
inline _GLIBCXX14_CONSTEXPR void
__advance(_BidirectionalIterator& __i, _Distance __n,
bidirectional_iterator_tag)
{
// concept requirements
__glibcxx_function_requires(_BidirectionalIteratorConcept<
_BidirectionalIterator>)
if (__n > 0)
while (__n--)
++__i;
else
while (__n++)
--__i;
}
编译期
的过程
template<unsigned n>
struct Factorial {
enum {
value = n * Factorial<n - 1>::value
};
};
template<>
struct Factorial<0> {
enum {
value = 1
};
};
int main() {
cout << Factorial<5>::value << endl; // 120
cout << Factorial<10>::value << endl; // 3628800
return 0;
}
主要是C++内存管理,在《STL源码剖析》中的空间配置器(allocator)篇章也着重讲了内存管理的内容,回头发现这一章还是很重要的。
两个主角:分配例程
和归还例程
(allocation and deallocation routines),即operator new
和operator delete
配角:new-handler
多线程环境下的内存管理很重要。由于heap是一个可被改动的全局性资源,如果没有适当的同步控制,一旦使用无锁算法或精心防止并发访问,调用内存例程可能很容易导致管理heap的数据结构内容败坏。
operator new
和operator delete
分配单一对象,operator new[]
和operator delete[]
分配一组对象
STL容器所使用的的heap内存由容器自带或分配的allocator来管理,而不是new和delete直接管理,这也是《STL源码剖析》第二章的主要分析内容。
区分new
、operator new
和placement new
,delete亦是。
set_new_handler
函数传入函数指针,设置好错误处理函数(new-handler)// 标准库中的定义
namespace std
{
// 函数指针
typedef void (*new_handler)();
// 设置错误处理函数(new-handler)
new_handler set_new_handler(new_handler) throw();
}
// 举例
void OutOfMem(){
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main{
// 两者都可以
// 1.使用空值,也会抛出异常
// terminate called after throwing an instance of 'std::bad_alloc'
// what(): std::bad_alloc
std::set_new_handler(nullptr);
// 2.使用自己定义的
// Unable to satisfy request for memory
std::set_new_handler(OutOfMem);
int* p_big_data_array = new int[100000000000L];
return 0;
}
替换编译器提供的operator new和operator delete的理由如下: