Skip to content

C++ 面向对象高级开发 侯捷

Posted on:2023.03.08

TOC

Open TOC

C++ 中 struct 与 class 的区别与比较

struct A {
};
class B : A { // 默认为 private 继承
};
struct C : B { // 默认为 public 继承
};

在 C 中使用结构体时需要加上 struct,或者对结构体使用 typedef 取别名,而 C++ 可直接使用

Object Based

complex 类

string 类

必须有 copy ctor 和 copy op=

Header 中的防卫式声明

#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif

inline 内联函数

在类内部定义的成员函数默认为 inline

inline 仅建议编译器内联

access level 访问级别

constructor 构造函数

default argument

可能引发构造函数 overloading 冲突

initialization list

发生在 assignments 之前

设计模式 - Singleton - 构造函数 private

class A {
public:
static A &getInstance();
A(const A &rhs) = delete;
void setup() {}
private:
A() = default;
};
A &A::getInstance() {
static A a;
return a;
}
int main() {
A::getInstance().setup();
}

考虑 delete 关键字

此处不将 static A a 放在类声明里面,是考虑到 lazy load

const member functions 常量成员函数

参数传递:pass by value vs. pass by reference (to const)

Item 41: 对于那些可移动总是被拷贝的形参使用传值方式

因为引用传递参数和值传递参数的用法相同,所以两个函数的函数签名 (signature) 相同,不能同时存在

double imag(const double &im);
double imag(const double im);

返回值传递:return by value vs. return by reference (to const)

传递者无需知道接收者是以 reference 形式接收

与指针对比

  • 若返回值类型为 pointer,则必须返回一个 pointer
  • 若返回值类型为 reference,可以返回一个 object,也可以返回一个 reference

friend 友元

相同 class 的各个 objects 互为 friends

class complex {
public:
explicit complex(double r = 0, double i = 0)
: re(r), im(i) {}
double func(const complex &param) { return param.re + param.im; }
private:
double re, im;
};
int main() {
complex c1(2, 1);
complex c2;
c2.func(c1);
}

operator overloading (成员函数) this

隐式 this

03a941372f254b4c97f0f1b112d66ae4.png

operator overloading (非成员函数) 无 this

重载 cout

ostream &
operator<<(ostream &os, const complex &x) {
return os << '(' << real(x) << ','
<< imag(x) << ')';
}

temp object (临时对象) typename ()

绝不可 return by reference

因为返回的必定是个 local object

inline complex
operator+(const complex &x, const complex &y) {
return complex(real(x) + real(y),
imag(x) + imag(y));
}
inline complex
operator+(const complex &x, double y) {
return complex(real(x) + y, imag(x));
}
inline complex
operator+(double x, const complex &y) {
return complex(x + real(y), imag(y));
}

Big Three

class String {
public:
String(const char *cstr = 0);
String(const String &str);
String &operator=(const String &str);
~String();
char *get_c_str() const { return m_data; }
private:
char *m_data;
};

必须是 pass by reference

如果类 A 的拷⻉构造函数的参数不是引⽤传递,⽽是采⽤值传递,那么就⼜需要为了创建传递给拷⻉构造函数的参数的临时对象,⽽⼜⼀次调⽤类 A 的拷⻉构造函数,这就是⼀个⽆限递归

检测 self assignment

inline String &String::operator=(const String &str) {
if (this == &str) // or (*this == str)
return *this;
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}

生命期

探索 new 操作

bac76c714673451d9002f223f48bd977.png

探索 delete 操作

3f92f84792394f458387ad4de840c8bb.png

探索创建对象的内存分配情况

array new 一定要搭配 array delete

12c1bc509bed4beca9c86991a89ef19d.png

32 位环境下

注意内存分配的 header 和 footer 为 21h,代表大小为 32 且已分配

如果不使用 array delete,实际上分配给类本身内存可以被完整回收,但是构造函数中分配给 data 的内存会泄露

如果换成 complex 类,也许就不会发生内存泄漏

进一步补充:static

static 成员变量,类的里面是声明,类的外面才是定义 / 分配内存

调用 static 成员函数的方式有二

  1. 通过 object 调用
  2. 通过 class name 调用

进一步补充:namespace

using namespace std;
using std::cout;

进一步补充:cout

虚继承

C++ 虚继承和虚基类详解

Object Oriented

Composition

构造与析构顺序

默认调用 default 构造函数

d710c3f578244d8baa3c5b380a579aa9.png

设计模式 - Adapter

queue 和 deque

template<class T>
class queue {
...
protected:
deque <T> c; // 底層容器
public: // 以下完全利用 c 的操作函數完成
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
reference back() { return c.back(); }
void push(const value_type &x) { c.push_back(x); }
void pop() { c.pop_front(); }
};

Delegation (Composition by reference)

设计模式 - Handle / Body or Pointer to Implementation or PIMPL

编译防火墙 C++ 的 Pimpl 惯用法解析

Inheritance

构造与析构顺序

d03ee2bb56884c96bc06dc8da407b8c2.png

Inheritance with virtual

设计模式 - Template Method

d3197711cb08462b9459ea1569e9f620.png

Inheritance + Composition

构造与析构顺序

5ced589f571945ce9630a12723016509.png

Delegation + Inheritance

设计模式 - Observer

fe99f1ed866c496a8544d0f39af82392.png

设计模式 - Composite

a2af953e4b494751923421517654ab8a.png

设计模式 - Prototype

ae1c0558b048407db65c3db096c3f9eb.png

转换函数

conversion function

把类类型转换为其他类型(基本类型、类类型)

87985432248348939023a05aca9f6ee4.png

在 STL 中的实例 - 设计模式 - 代理模式

ba2b04b00a334e6e8c92eb38b5e491a5.png

non-explicit-one-argument ctor

把其他类型(隐式)转换为类类型

配合 conversion function 可能引发二义性

在 non-explicit-one-argument ctor 或者 conversion function 前面加上 explicit,只能通过显式地进行构造

class F {
private:
int n_{};
int d_{};
public:
operator double() const {
return (double) n_ / d_;
}
F(int n, int d = 1) : n_{n}, d_{d} {}
F operator+(const F &f) {
int n1 = this->n_;
int n2 = f.n_;
int d1 = this->d_;
int d2 = f.d_;
this->n_ = n1 * d2 + d1 * n2;
this->d_ = d1 * d2;
return *this;
}
};
int main() {
F f(1, 2);
F g = f + 2;
}

pointer-like classes

重载运算符 *->

关于智能指针

dd6b654c247b42aa88b9be9e467ce55a.png

注意此处 -> 被消耗后仍然存在,这是语法定义

关于迭代器

60262abeb1f2496aa985c59e923561e3.png

function-like classes

仿函数,函数对象

重载运算符 ()

#include <iostream>
using std::cout;
template<class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
};
template<class Pair>
struct select1st {
const typename Pair::first_type &
operator()(const Pair &x) const { return x.first; }
};
int main() {
using P = pair<int, int>;
P p;
cout << select1st<P>()(p);
}

class template 类模板

template<typename T>
class complex {
public:
complex(T r = 0, T i = 0)
: re(r), im(i) {}
complex &operator+=(const complex &);
T real() const { return re; }
T imag() const { return im; }
private:
T re, im;
friend complex &__doapl(complex *, const complex &);
};

function template 函数模板

505a5782657c44799563f74433c3ee8d.png

member template 成员模板

template<class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
pair(const T1 &a, const T2 &b) : first(a), second(b) {}
template<class U1, class U2>
explicit pair(const pair<U1, U2> &p) : first(p.first), second(p.second) {}
};
struct Base1 {
};
struct Base2 {
};
struct Derived1 : Base1 {
};
struct Derived2 : Base2 {
};
int main() {
pair<Derived1, Derived2> p1;
pair<Base1, Base2> p2(p1);
}

这种结构通常用于实现子类到父类的转换

另一个例子是智能指针

f81155934f0d4f4c8c39c30f19441171.png

specialization 模版特化

#include <cstddef>
template<class Key>
struct hash {
// ...
};
template<>
struct hash<char> {
size_t operator()(char x) const { return x; }
};
template<>
struct hash<int> {
size_t operator()(char x) const { return x; }
};
template<>
struct hash<long> {
size_t operator()(char x) const { return x; }
};

上述代码实现针对 charintlong 这三个数据类型的 hash 使用指定代码,其它数据类型使用默认的通用代码

partial specialization 模版偏特化

template<typename T, typename Alloc>
class vector {
};
template<typename Alloc>
class vector<bool, Alloc> { // 指定了第一个参数类型
};
#include <string>
using std::string;
template<typename T>
class C {
};
template<typename T>
class C<T *> { // 指定了参数类型为指针类型
};
int main() {
C<string> obj1;
C<string *> obj2;
}

template template parameter 模板模板参数

一个模板的参数是模板类型,如

template <typename T, typename Cont = std::vector<T>>
class Stack {
private:
Cont elems; // elements
// ......
};

实例化

Stack<int, std::deque<int>> intStack;

可以看到上面这样实例化时,需要指定 int 类型两次,而且这个类型是一样的,能不能实例化直接写成

Stack<int, std::deque> intStack;

很显然是可以的,使用模板的模板参数,声明形式类似

template<typename T, template<typename Elem> class Cont = std::vector>
class Stack {
private:
Cont<T> elems; // elements
// ......
};

上面声明中的第二个模板参数 Cont 是一个类模板,注意声明中要用到关键字 class

在 C++17 之后,模板的模板参数中的 class 也可以替换成 typename

只有类模板可以作为模板参数,这样就可以允许我们在声明 Stack 类模板的时候只指定容器的类型而不去指定容器中元素的类型

一个较为复杂的例子

#include <iostream>
#include <vector>
#include <deque>
#include <cassert>
template<typename T,
template<typename Elem,
typename = std::allocator<Elem>>
class Cont = std::deque>
class Stack {
private:
Cont<T> elems; // elements
public:
void push(T const &); // push element
void pop(); // pop element
T const &top() const; // return top element
[[nodiscard]] bool empty() const // return whether the stack is empty
{
return elems.empty();
}
// assign stack of elements of type T2
template<typename T2,
template<typename Elem2,
typename = std::allocator<Elem2>>
class Cont2>
Stack<T, Cont> &operator=(Stack<T2, Cont2> const &);
// to get access to private members of any Stack with elements of type T2:
template<typename, template<typename, typename> class>
friend
class Stack;
};
template<typename T, template<typename, typename> class Cont>
void Stack<T, Cont>::push(T const &elem) {
elems.push_back(elem); // append copy of passed elem
}
template<typename T, template<typename, typename> class Cont>
void Stack<T, Cont>::pop() {
assert(!elems.empty());
elems.pop_back(); // remove last element
}
template<typename T, template<typename, typename> class Cont>
T const &Stack<T, Cont>::top() const {
assert(!elems.empty());
return elems.back(); // return last element
}
template<typename T, template<typename, typename> class Cont>
template<typename T2, template<typename, typename> class Cont2>
Stack<T, Cont> &Stack<T, Cont>::operator=(Stack<T2, Cont2> const &op2) {
elems.clear(); // remove existing elements
elems.insert(elems.begin(),
op2.elems.begin(),
op2.elems.end());
return *this;
}
int main() {
Stack<int> iStack; // stacks of ints
Stack<float> fStack; // stacks of floats
// manipulate int stack
iStack.push(1);
iStack.push(2);
std::cout << "iStack.top(): " << iStack.top() << '\n';
// manipulate float stack
fStack.push(3.3);
std::cout << "fStack.top(): " << fStack.top() << '\n';
// assign stack of different type and manipulate again
fStack = iStack;
fStack.push(4.4);
std::cout << "fStack.top(): " << fStack.top() << '\n';
// stack for doubles using a vector as an internal container
Stack<double, std::vector> vStack;
vStack.push(5.5);
vStack.push(6.6);
std::cout << "vStack.top(): " << vStack.top() << '\n';
vStack = fStack;
std::cout << "vStack: ";
while (!vStack.empty()) {
std::cout << vStack.top() << ' ';
vStack.pop();
}
std::cout << '\n';
}

C++ 标准库速览

reference

编译器其实把 reference 视作一种 pointer

#include <iostream>
using std::cout, std::endl;
typedef struct Stag {
int a, b, c, d;
} S;
int main(int argc, char **argv) {
double x = 0;
double *p = &x;
double &r = x;
cout << sizeof(x) << endl;
cout << sizeof(p) << endl;
cout << sizeof(r) << endl;
cout << p << endl;
cout << *p << endl;
cout << x << endl;
cout << r << endl;
cout << &x << endl;
cout << &r << endl;
S s;
S &rs = s;
cout << sizeof(s) << endl;
cout << sizeof(rs) << endl;
cout << &s << endl;
cout << &rs << endl;
return 0;
}

Object Model 对象模型

关于 vptr 和 vtbl

在讲虚指针和虚表之前,先要知道

8be488e2075641bc80edcd217a436ec6.png

三个条件

关于 this

c263f00279b540c79483426d96f97d3d.png

一个隐式的 this 参数

对成员函数的调用会添加 this->,从而产生可能的 Dynamic Binding

关于 Dynamic Binding

关于 new 和 delete

new 和 delete 作为表达式,不可以重载

但是其内部的 operator new 和 operator delete 作为操作符,可以重载

重载全局函数

::operator new
::operator new[]
::operator delete
::operator delete[]

重载成员函数

member new
member new[]
member delete
member delete[]

placement new

basic_string 使用 new(extra) 扩充申请量

d6ceb02350f74fdfb40ddf1ade410e79.png

Rep 为引用计数,extra 存放实际的 string 内容

示例

#include <iostream>
#include <cstdio>
using namespace std;
void *operator new(size_t size) {
::printf("global operator new with size %zu\n", size);
return ::malloc(size);
}
void *operator new[](size_t size) {
::printf("global operator new[] with size %zu\n", size);
return ::malloc(size);
}
void operator delete(void *ptr) noexcept {
::printf("global operator delete with ptr %p\n", ptr);
return ::free(ptr);
}
void operator delete(void *ptr, size_t size) noexcept { // with size_t arg
::printf("global operator delete with ptr %p size %zu\n", ptr, size);
return ::free(ptr);
}
void operator delete[](void *ptr) noexcept {
::printf("global operator delete[] with ptr %p\n", ptr);
return ::free(ptr);
}
struct Bad : exception {
};
struct Foo {
int id_{};
Foo() = default;
explicit Foo(int id) : id_{id} { throw Bad(); }
virtual ~Foo() = default;
static void *operator new(size_t size) {
::printf("member operator new with size %zu\n", size);
return ::malloc(size);
}
static void *operator new[](size_t size) {
::printf("member operator new[] with size %zu\n", size);
return ::malloc(size);
}
static void operator delete(void *ptr) {
::printf("member operator delete with ptr %p\n", ptr);
::free(ptr);
}
static void operator delete[](void *ptr) {
::printf("member operator delete[] with ptr %p\n", ptr);
::free(ptr);
}
// placement new functions
static void *operator new(size_t size, void *start) {
::printf("member operator new with size %zu start %p\n", size, start);
return start;
}
static void *operator new(size_t size, size_t extra) {
::printf("member operator new with size %zu extra %zu\n", size, extra);
return ::malloc(size + extra);
}
// placement delete functions
static void operator delete(void *ptr, void *start) {
::printf("member operator delete with ptr %p start %p\n", ptr, start);
}
static void operator delete(void *ptr, size_t extra) {
::printf("member operator delete with ptr %p extra %zu\n", ptr, extra);
}
};
int main() {
{
int *foo = new int;
delete foo;
}
{
Foo *foo = new Foo; // 16 = roundup(4 + 8, 8)
delete foo;
}
{
Foo *foo = new Foo[4]; // 72 = 4 * 16 + 8
delete[] foo;
}
{
Foo *foo = ::new Foo[4]; // global
::delete[] foo;
}
{
void *ptr = ::malloc(sizeof(Foo));
// placement new expression
Foo *foo = new(ptr) Foo;
// **not** placement delete expression
// delete foo;
}
{
void *ptr = ::malloc(sizeof(Foo));
try {
Foo *foo = new(ptr) Foo(1);
} catch (Bad &bad) {
::printf("%s\n", bad.what());
}
}
{
try {
Foo *foo = new(4) Foo(1);
} catch (Bad &bad) {
::printf("%s\n", bad.what());
}
}
}

注意 placement delete function 是直接调用不到的东西,当 placement new expression 调用 placement new function,如果构造函数函数构造的时候发生了异常,这个时候要防止内存泄露,那么要清理掉已分配的内存,就需要这个 placement delete function

另外,对于 operator delete 的重载,可以添加可选的 size_t 参数,不过似乎会与 placement delete functions 冲突

C++ 2.0

variadic templates

数量不定的模版参数

20caa5493f1e4d87a032afea84e0a894.png

实现递归函数,注意边界情况

#include <iostream>
#include <sstream>
using namespace std;
void print() { // base case
}
template<typename T>
string rep(const T &t) {
ostringstream ret;
ret << t;
return ret.str();
}
template<typename T, typename... Types>
void print(const T &first, const Types &...args) {
cout << sizeof...(args) << endl;
cout << rep(first) << endl;
print(rep(args)...);
}
int main() {
print("114514", 2, 3.0);
}

另外标准库中的 tuple 利用 variadic templates,使用递归继承的方式来实现

C++ Tuple 异质容器的实现

nullptr

Item 8: 优先考虑 nullptr 而非 0 和 NULL

uniform initialization

#include <iostream>
using namespace std;
struct Foo {
Foo(int a, int b) {
cout << "Foo::Foo(int, int)" << endl;
}
Foo(initializer_list<int> list) {
cout << "Foo::Foo(initializer_list<int>)" << endl;
}
};
int main() {
Foo foo(1, 2);
Foo bar{1, 2};
}

explicit for ctors taking more than one argument

在多个实参的 ctors 上也可以加上关键字 explicit 来禁止做隐式转化

#include <iostream>
using namespace std;
struct Foo {
Foo(int a, int b) {
cout << "Foo::Foo(int, int)" << endl;
}
explicit Foo(int a, int b, int c) {
cout << "explicit Foo::Foo(int, int, int)" << endl;
}
};
int main() {
Foo foo = {1, 2};
Foo bar{1, 2, 3};
// Foo bar = {1, 2, 3};
}

ranged-base for

for (declaration : collection)
{
statement;
}

实际上等价于

for (auto _pos = collection.begin(); _pos != collection.end(); ++_pos)
{
declaration = *_pos;
statement;
}

default 和 delete

Item 17: 理解特殊成员函数函数的生成

换句话说

三五法则

Item 11: 优先考虑使用 deleted 函数而非使用未定义的私有声明

using

alias template

在 C++17 以前,下述用法会报错

template<typename T, template<typename Elem> class Cont = std::vector>
class Stack {
private:
Cont<T> elems; // elements
// ......
};
int main() {
Stack<int, std::vector> st{};
}

因为 std::vector 声明为

template <typename T, typename Allocator = std::allocator<T>> class vector;

这里不会自动 deduce 出 Allocator 模板参数

所以需要写成

template<typename T, template<typename Elem> class Cont = std::vector>
class Stack {
private:
Cont<T> elems; // elements
// ......
};
template<typename T>
using Vec = std::vector<T, std::allocator<T>>;
int main() {
Stack<int, Vec> st{};
}

这便使用了 alias template

在 C++17 之后,第一种写法可以通过

史上最全 C++17 新特性

Matching of template template-arguments excludes compatible templates

type alias

using func = void(*)(int, int);
typedef void(*func)(int, int);

noexcept

Item 14: 如果函数不抛出异常请使用 noexcept

noexcept 允许编译器生成更好的目标代码

unwind 调用栈和可能 unwind 调用栈两者对于代码生成有非常大的影响

对于 std::vector 而言,其扩容默认为复制操作,可以提供异常安全保证,即如果在复制元素期间抛出异常,std::vector 状态保持不变

如果将复制操作替换为移动操作,除非知晓移动操作绝不抛异常,这时复制替换为移动就是安全的

C++11 后,默认情况下,内存释放函数和析构函数都是隐式 noexcept 的,如果一个对象的析构函数可能被标准库使用(比如在容器内或者被传给一个算法),析构函数又可能抛异常,那么程序的行为是未定义的

override

Item 12: 使用 override 声明重载函数

要想 override 一个函数,必须满足下列要求

class Widget {
public:
void doWork() &; // 只有 *this 为左值的时候才能被调用
void doWork() &&; // 只有 *this 为右值的时候才能被调用
};
Widget makeWidget(); // 工厂函数(返回右值)
Widget w; // 普通对象(左值)
w.doWork(); // 调用被左值引用限定修饰的 Widget::doWork 版本
// 即 Widget::doWork &
makeWidget().doWork(); // 调用被右值引用限定修饰的 Widget::doWork 版本
// 即 Widget::doWork &&

final

auto & decltype

auto

Item 1: 理解模板类型推导

Item 2: 理解 auto 类型推导

template<typename T>
void f(ParamType param);
f(expr);

需要根据 expr 推导出 ParamTypeT

auto 类型推导即为模板类型推导,例如

template<typename T>
void f(T& param);

此处 ParamType 即为 T&

f(expr);
auto &param = expr;

ParamType 是指针或者引用,但不是万能引用

expr 是个引用类型,先将引用部分忽略

expr 的类型和 ParamType 的类型执行模式匹配,来决定 T 的类型

#include <iostream>
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
int main() {
// 变量类型
int x = 27; // x 的类型是 int
const int cx = x; // cx 的类型是 const int
const int &rx = x; // rx 是 cx 类型为 const int 的引用
// auto 引用变量
auto &ax = x; // ax 变量的类型为 int&
auto &acx = cx; // acx 变量的类型为 const int&
auto &arx = rx; // arx 变量的类型为 const int&
std::cout << "ax: " << type_id_with_cvr<decltype(ax)>() << std::endl;
std::cout << "acx: " << type_id_with_cvr<decltype(acx)>() << std::endl;
std::cout << "arx: " << type_id_with_cvr<decltype(arx)>() << std::endl;
}

总结为

ParamType 是万能引用

如果 expr 是个左值,TParamType 都会被推导为左值引用

在模版类型推导中,T 唯一被推导为左值引用的情况

如果 expr 是个右值,则按照第一种规则进行推导

#include <iostream>
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
int main() {
// 变量类型
int x = 27; // x 的类型是 int
const int cx = x; // cx 的类型是 const int
// auto 声明变量
auto &&ax = x; // x 类型 int,且是左值,所以 ax 是左值引用,类型为 int&
auto &&acx = cx; // cx 类型 const int, 且是左值,所以 acx 是左值引用,类型为 const int &
auto &&alx = 27; // 27 类型 int,为右值,所以 alx 以右值引用,类型为 int&&
std::cout << "ax: " << type_id_with_cvr<decltype(ax)>() << std::endl;
std::cout << "acx: " << type_id_with_cvr<decltype(acx)>() << std::endl;
std::cout << "alx: " << type_id_with_cvr<decltype(alx)>() << std::endl;
}

ParamType 既非指针也非引用

类似第一种规则 (忽略引用性),但也需要忽略常量性 (和易变性)

#include <iostream>
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
int main() {
// 变量类型
int x = 27; // x 的类型是 int
const int cx = x; // cx 的类型是 const int
// auto 声明变量
auto ax = x; // ax 的类型是 int
auto acx = cx; // acx 的类型是 int
std::cout << "ax: " << type_id_with_cvr<decltype(ax)>() << std::endl;
std::cout << "acx: " << type_id_with_cvr<decltype(acx)>() << std::endl;
}

区分 auto 和 decltype(auto)

Item 3: 理解 decltype

举几个例子,对于

template<typename Container, typename Index>
auto authAndAccess(Container &c, Index i) -> decltype(c[i]) {
authenticateUser();
return c[i];
}

推导规则为 decltype 的规则

对于

template<typename Container, typename Index>
auto authAndAccess(Container &c, Index i) {
authenticateUser();
return c[i];
}

推导规则为 auto 的规则 (模板类型推导)

所以会导致下述代码失败

std::deque<int> d;
authAndAccess(d, 5) = 10;

若改成

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container &c, Index i) {
authenticateUser();
return c[i];
}

推导规则为 decltype 的规则

区分 decltype(E) 和 decltype((E))

https://zhuanlan.zhihu.com/p/593957444

在开始值类别之前,我们先回顾一下 decltype 有哪些作用。虽然 decltype 只是一个关键字,但是 decltype(E) 对于不同的 E 是两种完全不同的运算:

lambda

#include <iostream>
int main() {
int id = 0;
auto f = [id]()mutable noexcept -> void {
std::cout << "id: " << id << std::endl;
++id;
};
id = 42;
f();
f();
f();
std::cout << id << std::endl;
}

注意这里的 mutable noexcept -> void

lambda 本质上是一个 functor,所以上述程序等价于

#include <iostream>
int main() {
int id = 0;
class UnNamedLocalFunction {
private:
int id_;
public:
explicit UnNamedLocalFunction(int id) : id_{id} {}
void operator()() noexcept {
std::cout << "id: " << id_ << std::endl;
++id_;
}
};
UnNamedLocalFunction f(id);
id = 42;
f();
f();
f();
std::cout << id << std::endl;
}

这说明了 id = 42; 的赋值不会影响内部的 id_

rvalue references

右值引用是一种新的引用类型,它可以用来减少不必要的拷贝

当赋值运算的右边是一个右值的话,那么左边的对象可以右边对象的资源

一般的赋值运算,实际上是 operator=,在 C++11 之前,只有 copy op=

必须有语法让我们写出一个专门处理右值的所谓 move assignment 函数

如果一个左值也想作为右值引用来使用的话,可以使用 std::move,但是必须保证 obj 后续不再使用,否则行为未定义

下面是一个具有 move aware 的 string 类,需要注意这里的移动构造和移动赋值需要加上 noexcept

#include <cstddef>
#include <cstring>
#include <string>
class MyString {
public:
static size_t DCtor; // 统计 default-ctor 调用次数
static size_t Ctor; // 统计 ctor 调用次数
static size_t CCtor; // 统计 copy-ctor 调用次数
static size_t CAsgn; // 统计 copy-asgn 调用次数
static size_t MCtor; // 统计 move-ctor 调用次数
static size_t MAsgn; // 统计 move-asgn 调用次数
static size_t Dtor; // 统计 dtor 调用次数
private:
char *_data{};
size_t _len;
void _init_data(const char *s) {
_data = new char[_len + 1];
memcpy(_data, s, _len);
_data[_len] = '\0';
}
public:
// default ctor
MyString() : _data(nullptr), _len(0) { ++DCtor; }
// constructor
explicit MyString(const char *p) : _len(strlen(p)) {
++Ctor;
_init_data(p);
}
// copy constructor
MyString(const MyString &str) : _len(str._len) {
++CCtor;
_init_data(str._data);
}
// move constructor
MyString(MyString &&str) noexcept
: _data(str._data), _len(str._len) {
++MCtor;
str._len = 0;
str._data = nullptr;
}
// copy assignment
MyString &operator=(const MyString &str) {
++CAsgn;
if (this != &str) {
if (_data) {
delete _data;
}
_len = str._len;
_init_data(str._data);
}
return *this;
}
// move assignment
MyString &operator=(MyString &&str) noexcept {
++MAsgn;
if (this != &str) {
if (_data) {
delete _data;
}
_len = str._len;
_data = str._data;
str._len = 0;
str._data = nullptr;
}
return *this;
}
// dtor
virtual ~MyString() {
++Dtor;
if (_data) {
delete _data;
}
}
bool operator<(const MyString &rhs) const {
return std::string(this->_data) < std::string(rhs._data);
}
bool operator==(const MyString &rhs) const {
return std::string(this->_data) == std::string(rhs._data);
}
char *get() const { return _data; }
};
size_t MyString::DCtor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;
namespace std {
template<>
struct hash<MyString> {
size_t operator()(const MyString &s) const noexcept {
return hash<string>()(string(s.get()));
}
};
}

perfect forwarding

非常重要的一点是要牢记形参永远是左值,即使它的类型是一个右值引用

比如,假设

void f(Widget&& w);

形参 w 是一个左值,即使传入的实参是一个右值

所以会出现下面的情况

#include <iostream>
using namespace std;
void process(int &i) {
cout << "void process(int &i)" << endl;
}
void process(int &&i) {
cout << "void process(int &&i)" << endl;
}
void forward(int &&i) {
cout << "void forward(int &&i)" << endl;
process(i);
}
int main() {
forward(1);
// output
// void forward(int &&i)
// void process(int &i)
}

当出现对右值引用的传递时,就会出现问题

需要将 forward 修改为

template<typename T>
void forward(T &&i) {
cout << "void forward(T &&i)" << endl;
process(std::forward<T>(i));
}

实际上这里的 T && 是一个万能引用,既可以绑定到左值,也可以绑定到右值,其实现机制为引用折叠

int x = 1;
forward(x);
forward(1);

Item 25: 对于右值引用使用 std::move,对于万能引用使用 std::forward

inline variable

SFINAE

smart pointers

Item 19: 对于共享资源使用 std::shared_ptr

手写 std::shared_ptr

#include <iostream>
#include <memory>
#include <atomic>
using namespace std;
namespace smart_pointer {
template<typename T>
struct defaultDeleter {
void operator()(const T *ptr) {
if (ptr) {
delete ptr;
ptr = nullptr;
}
}
};
template<typename T, typename Deleter=defaultDeleter<T> >
class shared_ptr {
public:
shared_ptr();
explicit shared_ptr(T *ptr);
shared_ptr(const shared_ptr &sp);
shared_ptr(shared_ptr &&sp) noexcept;
~shared_ptr();
T *operator->() const;
T &operator*() const;
operator bool() const;
shared_ptr &operator=(const shared_ptr &sp);
shared_ptr &operator=(shared_ptr &&sp);
T *get() const;
void reset(T *ptr);
void swap(shared_ptr &sp);
int count();
private:
atomic<int> *use_count{};
T *ptr;
};
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::shared_ptr() = default;
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::shared_ptr(T *_ptr)
: ptr(_ptr), use_count(new atomic<int>(1)) {}
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::shared_ptr(const shared_ptr &sp)
: ptr(sp.ptr), use_count(sp.use_count) {
++*use_count;
}
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::shared_ptr(shared_ptr &&sp) noexcept {
std::swap(ptr, sp.ptr);
std::swap(use_count, sp.use_count);
}
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::~shared_ptr() {
if (ptr) {
--*use_count;
if (*use_count <= 0) {
Deleter()(ptr);
delete use_count;
cout << "shared_ptr dctor" << endl;
}
}
}
template<typename T, typename Deleter>
T *shared_ptr<T, Deleter>::operator->() const {
return ptr;
}
template<typename T, typename Deleter>
T &shared_ptr<T, Deleter>::operator*() const {
return *ptr;
}
template<typename T, typename Deleter>
shared_ptr<T, Deleter>::operator bool() const {
return ptr != nullptr;
}
template<typename T, typename Deleter>
shared_ptr<T, Deleter> &shared_ptr<T, Deleter>::operator=(const shared_ptr<T, Deleter> &sp) {
if (sp.ptr == ptr) {
return *this;
}
ptr = sp.ptr;
use_count = sp.use_count;
++*use_count;
return *this;
}
template<typename T, typename Deleter>
shared_ptr<T, Deleter> &shared_ptr<T, Deleter>::operator=(shared_ptr<T, Deleter> &&sp) {
std::swap(ptr, sp.ptr);
std::swap(use_count, sp.use_count);
return *this;
}
template<typename T, typename Deleter>
T *shared_ptr<T, Deleter>::get() const {
return ptr;
}
template<typename T, typename Deleter>
void shared_ptr<T, Deleter>::reset(T *_ptr) {
shared_ptr<T, Deleter>().swap(*this);
ptr = _ptr;
use_count = new atomic<int>(1);
}
template<typename T, typename Deleter>
void shared_ptr<T, Deleter>::swap(shared_ptr<T, Deleter> &sp) {
std::swap(ptr, sp.ptr);
std::swap(use_count, sp.use_count);
}
template<typename T, typename Deleter>
int shared_ptr<T, Deleter>::count() {
return *use_count;
}
}; // namespace smart_pointer
int main(int argc, char *argv[]) {
{
smart_pointer::shared_ptr<int> p(new int(12));
smart_pointer::shared_ptr<int> p1(p);
*p1 = 2;
cout << *p << endl;
cout << p.count() << endl;
// 异常安全测试
p = p;
cout << *p << endl;
cout << p.count() << endl;
}
{
smart_pointer::shared_ptr<int> p(new int(12));
p.reset(new int(13));
cout << *p << endl;
// 右值赋值测试
p = smart_pointer::shared_ptr<int>(new int(14));
cout << *p << endl;
// 移动构造测试
smart_pointer::shared_ptr<int> p1(smart_pointer::shared_ptr<int>(new int(15)));
cout << *p1 << endl;
}
return 0;
}