OOP
STL
list
c++11 之前 size() 可能为线性时间, 因为可能遍历整个链表; empty() 一定是常数时间
vector
vector<int> v = {1, 2, 3, 4};
vector<int> u;
copy(v.begin(), v.end(), back_inserter(u));
copy(u.begin(), u.end(), ostream_iterator<int>(cout, ", "));
vector<int> l;
copy(v.begin(), v.end(), front_inserter(l)); // error
copy(l.begin(), l.end(), ostream_iterator<int>(cout, ", "));
memory model
global vars: 通过 extern 在 .cpp 文件之间共享
static global vars: 拒绝跨文件访问
static local vars: 需要在内部声明, 在外部定义
reference
non-const 引用必须是左值
memory leak
#include<iostream>
#include<thread>
using namespace std;
void f(){
int *p = new int[1000];
cout << p[0] << endl;
}
int main(){
while(1){
f();
this_thread::sleep_for(1s);
}
return 0;
}
用这段代码观察内存泄露, 每秒内存占用增大 4K
#include<iostream>
#include<thread>
using namespace std;
struct A{
A(){}
~A(){}
};
int main(int argc, char** argv){
int size = 0;
if(argc > 1) size = atoi(argv[1]);
A* a = new A[size];
size_t *c = (size_t*) a;
cout << *(c - 1) << endl;
return 0;
}
动态内存分配的前 \(8\) 个字节为分配内存的大小
const
const int class_size = 12;
int finalGrade[class_size]; // ok
int x;
cin >> x;
const int size = x;
double classAverage[size]; // error
运行时常量不能, 因为编译前不确定
int a[] = {53,54,55};
int * const p = a; // p is const
*p = 20; // OK
p++; // ERROR
const int *p = a; // (*p) is const
*p = 20; // ERROR!
p++; // OK
int i;
const int ci = 3;
int* ip;
const int* cip;
ip = &i;
ip = &ci; // Error
cip = &i;
cip = &ci;
*ip = 54; // always legal
*cip = 54; // never legal
但是 non-const = const 不行, 指针可能改变原有的值
conversion
Can always treat a non-const value as const
You cannot treat a constant object as non-constant without an explicit cast const_cast
const function
struct X{
void foo() const{
}
void fool() {
}
}
int main(){
const X x;
x.foo(); // ok
x.fool(); // error
X y;
y.foo(); // ok
y.fool(); // ok
}
class
resolver
::<function name> 选择 global 的变量 / 函数
void S::f() {
::f(); // Would be recursive otherwise!
::a++; // Select the global 'a'
a--; // The 'a' at class scope
}
::f(), ::a 选择了类函数外面的 global 函数 / 变量
ctor
int a[5] = {1,2,3,4,5};
int b[6] = {5};
int c[] = {1,2,3,4}; // sizeof(c) / sizeof(*c)
struct X {
int i; float f; char c;
};
X x1 = {1, 2.2, 'c'};
X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} }
struct Y {
float f; int i; Y(int a);
};
Y y1[] = { Y(1), Y(2), Y(3) };
Y y2[2] = {Y(1)}; // Error, no default ctor
initializer list
按声明的顺序初始化, 而不是初始化列表的顺序
- Destroyed in the reverse order.
const
必须要在初始化列表 (ctor) 被初始化
同时不加 static 的类内 const 是运行时常数, 不能声明数组
struct X{
const int i; // must be initialized in Ctor
X() : i(0) {
}
}
private
struct 默认 public class 默认 private
private 针对于 class, 不针对与 object
所以如果有一个指针, 那么指针可以访问并改写 private 变量
struct b{
int a;
int c;
};
int *p = (int *)&b;
p++;
*p = 20; // change c
pointer
int * const p = a;
*p = 20; // ok
p++; // error
const int * p = a;
*p = 20; // error
p++; // ok
const char* s = "CCB";
const char* p = "CCB";
cout << (void*) s << " " << (void*) p << endl; // they are the same
#include <iostream>
struct X {
X() {
std::cout << "X::X()" << std::endl;
}
~X() {
std::cout << "X::~X()" << std::endl;
}
};
struct Y {
Y() {
std::cout << "Y::Y()" << std::endl;
}
~Y() {
std::cout << "Y::~Y()" << std::endl;
}
};
struct Parent {
Parent() {
std::cout << "Parent::Parent()" << std::endl;
}
~Parent() {
std::cout << "Parent::~Parent()" << std::endl;
}
X x;
};
struct Child : public Parent {
Child() {
std::cout << "Child::Child()" << std::endl;
}
~Child() {
std::cout << "Child::~Child()" << std::endl;
}
Y y;
};
int main() {
Parent a;
Child b;
Parent * c = new Child();
delete c;
}
/*
a:
X::X()
Parent::Parent()
Parent::~Parent()
X::~X()
*/
/*
b:
X::X()
Parent::Parent()
Y::Y()
Child::Child()
Child::~Child()
Y::~Y()
Parent::~Parent()
X::~X()
*/
/*
c: ~Child() not called
X::X()
Parent::Parent()
Y::Y()
Child::Child()
Parent::~Parent()
X::~X()
*/
static
static / dynamic binding
struct X{
static int n; // declaration
}
int X::n = 7; // definition
int main(){
n = 1; // if no "X::n = 7", then error: no definition
}
struct X{
static int data;
X() : data(0){
}
static void foo(int ii){
}
}
int main(){
X x = X();
x.foo(30);
X::foo(30); // ok, because of "static"
}
static 可以让变量 / 函数与类相关, 不与对象绑定
static variable
struct A{
static int data;
void setdata(int d){
data = d;
}
void print(){
cout << data << endl;
}
};
int A::data = 0;
int main(){
A a;
B b;
a.setdata(20);
a.print();
b.print();
return 0;
}
// 20
// 20
data 存放在外面, 只有一份, 所有对象共享
static function
与普通成员函数都存在代码段, 但不需要 this 指针, 也因此只能访问类的静态变量
inline
in assembly prospective
inline void f(int n){
return pow(n, 2);
}
int main(){
cout << f(2) << endl;
}
汇编:
call f;
call pow;
所以把里面内容展开了
再类的声明里面的函数自动都是 inline
inline in class
类里的成员函数默认是 inline, 允许重定义
在类外面定义, 同时要在两个文件中引用这个头文件, 就要加 inline 防止重定义报错
因为加 inline 将函数展开, 不会函数名重合
example
// h.h
#ifndef __H_H__
#define __H_H__
#include <iostream>
void f(int t);
void f(int t){
std::cout << t << std::endl;
}
#endif
// a.h
#ifndef __A_H__
#define __A_H__
#include "h.h"
void g();
#endif
// a.cpp
#include "a.h"
void g(){
int a = 10;
f(a);
}
// main.cpp
#include <bits/stdc++.h>
#include "a.h"
int main(){
g();
return 0;
}
有报错:
D:/TDM-GCC/bin/../lib/gcc/x86_64-w64-mingw32/10.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\yyr04\AppData\Local\Temp\cczJPS8q.o:a.cpp:(.text+0x0): multiple definition of `f()'; C:\Users\yyr04\AppData\Local\Temp\ccljAvCe.o:main.cpp:(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
因为在 main.cpp 和 a.cpp 中都引入了 a.h, 即都引入了 h.h, 所以定义了两份 f()
-
在
f()前加inline解决// h.h #ifndef __H_H__ #define __H_H__ #include <iostream> inline void f(int t); inline void f(int t){ std::cout << t << std::endl; } #endif这样把
f()展开, 没有重定义 -
在
f()前加static解决// h.h #ifndef __H_H__ #define __H_H__ #include <iostream> static void f(int t); static void f(int t){ std::cout << t << std::endl; } #endif这样
a.cpp和main.cpp中各有一份f(), 且地址不同, 所以不会冲突 -
f()改成模板函数// h.h #ifndef __H_H__ #define __H_H__ #include <iostream> template<class T> void f(int t); template<class T> void f(int t){ std::cout << t << std::endl; } #endif这样只实例化了
a.cpp中的f()
inline vs. marco
#define unsafe(i) \
((i)>=0?(i):-(i))
int f();
int main() {
ans = unsafe(x++);
// ans = 1, x = 2
ans = unsafe(f());
}
inline int safe(int i) { return i>=0 ? i:-i; }
int f();
int main() {
ans = safe(x++);
// ans = 0, x = 1
ans = safe(f());
}
inline 能让 x 像正常函数一样, 而不是像宏直接展开填入 x++ 使得 x 加了两次
virtual
虚指针 vptr 在截断赋值时不会被处理
class Base {
public:
virtual void foo() { cout << "Base::foo()" << endl; }
};
class Derived : public Base {
public:
void foo() override{
cout << "overrided from ";
Base::foo();
cout << "Derived::foo()" << endl;
}
};
int main()
{
Base b;
Base *d = new Derived();
d->foo();
b = *d;
b.foo();
}
/*
overrided from Base::foo()
Derived::foo()
Base::foo()
*/
Base::foo() 是静态绑定, 编译时确定
d->foo() 和 b.foo() 是动态绑定, 运行时确定, 通过查虚函数表实现
同时由于截断赋值时 b 的 vptr 没有变成 d 的 vptr, 所以 b.foo() 调用了 Base::foo()
error
void f(){
X x; // 栈上的对象, 退栈自动析构
Y *p = new Y(); // new 时抛出异常, new 会收回分配的内存, 不会内存泄露
}
但如果 catch 了异常, 则不会正常析构:
#include <iostream>
using namespace std;
struct X{
int *v;
X() : v(new int[10]){
cout << "X::X()" << endl;
if(true){
throw 1;
}
}
~X(){
cout << "X::~X()" << endl;
delete[] v, cout << "deleting v\n";
}
};
void f(){
try{
X x;
}catch(...){
cout << "catching exception" << endl;
}
// do sth
}
int main()
{
f();
return 0;
}
之前的写法, X x; 后程序直接异常退出
但是 catch 后程序会正常执行至结束, 但 X 的构造函数失败, 不会执行析构函数, 所以在 //do sth 时之前的内存泄露
改写成:
#include <iostream>
using namespace std;
struct X{
int *v;
X() : v(nullptr){
cout << "X::X()" << endl;
}
void init() {
v = new int[10]();
if(true){
throw 1;
}
}
~X(){
cout << "X::~X()" << endl;
delete[] v, cout << "deleting v\n";
}
};
void f(){
try{
X x;
x.init();
}catch(...){
cout << "catching exception" << endl;
}
// do sth
}
int main()
{
f();
return 0;
}
可以正常结束构造
或者改成:
X() : v(new int[10]){
cout << "X::X()" << endl;
if(true){
delete[] v, cout << "deleting v\n";
throw 1;
}
}
或者改成:
struct T{
int *v;
T(int *v) : v(v) {
cout << "T::T()\n";
}
~T(){
delete[] v;
cout << "T::~T()\n";
cout << "deleting v\n";
}
};
struct X{
T t;
X() : t(new int[10]()){
cout << "X::X()" << endl;
if(true){
throw 1;
}
}
~X(){
cout << "X::~X()" << endl;
}
};
void f(){
try{
X x;
}catch(...){
cout << "catching exception" << endl;
}
// do sth
}
这里 t 已经在初始化列表里被构造好, 所以 throw 时可以正常析构
就是不需要手动在 A::~A() 里释放
而 int *v 需要自行在 A::~A() 里释放
这种处理最好
事实上这等于
#include <memory>
struct X{
unique_ptr<int[]> t;
X() : t(new int[10]()){
cout << "X::X()" << endl;
if(true){
throw 1;
}
}
~X(){
cout << "X::~X()" << endl;
}
};
注意一般析构函数承诺 noexcept
如果在发生异常时触发的 T::~T() 中又发生异常, 程序直接退出, 且不进行正常的资源释放
如果析构函数内部发生异常, 要在内部直接 catch, 避免传播到外面
class resource{
public:
resource() = default;
~resource() {
}
resource(const resource &r){
throw runtime_error("Resource error");
}
};
class widget{
private:
int i;
string s;
resource *pr;
public:
widget() : i(0), pr(new resource){
}
~widget(){
delete pr;
}
widget(const widget &w) : i(w.i), s(w.s){
if(w.pr) pr = new resource(*w.pr);
else pr = nullptr;
}
widget& operator =(const widget& w){
if(this == &w) return *this;
i = w.i;
s = w.s;
delete pr;
pr = nullptr; // 如果下面的 new resource 抛出异常, 那么 pr 未改变, 在 delete 后称为悬挂指针
if(w.pr) pr = new resource(*w.pr);
return *this;
}
};
class resource{
public:
resource() = default;
~resource() {
}
resource(const resource &r){
throw runtime_error("Resource error");
}
};
class widget{
private:
int i;
string s;
unique_ptr<resource> pr;
public:
widget() : i(0), pr(new resource){
}
// ~widget(){
// delete pr;
// }
widget(const widget &w) : i(w.i), s(w.s){
if(w.pr) pr = make_unique<resource>(*w.pr);
}
widget& operator=(const widget &w){
if(this == &w) return *this;
widget tmp(w); // 只有拷贝构造成功, 才会执行下面的拷贝赋值
*this = move(tmp);
return *this;
}
widget& operator=(widget &&w) noexcept = default;
};
使用 unique_ptr 可以不用上面的判断, 直接使用拷贝构造
noexcept
移动构造函数加 noexcept 原因:
如果标准库 std::vector, std::string 等在扩充内存时, 会判断移动构造函数是否有 noexcept 约定, 从而决定是否使用移动构造函数
一般优先使用移动构造, 相比拷贝构造提升性能
同时 vector 如果移动过程中出异常, 可能原本对某个元素的控制权已经释放, 但是由于异常, 应该接受控制权的元素没有控制权, 这样不安全
upcast
一般来说基类指针指向的派生类对象析构只调用基类析构, 除非基类析构是 virtual
relaxation
class Expr{
public:
virtual Expr* newExpr();
virtual Expr& clone();
virtual Expr self();
};
class BinExpr : public Expr{
public:
virtual BinExpr* newExpr(); // ok
virtual BinExpr& clone(); // ok
virtual BinExpr self(); // error
};
直接返回 BinExpr 值与 Expr 是不同的
但是 BinExpr 指针与 Expr 指针可以向上造形
引用同理
smart pointer
{
uniqur_pre<resource> p1(new resource());
uniqur_pre<resource> p2(new resource(7));
p1 = p2; // error
p1 = std::move(p2); // ok
}
move 相当于将 p2 的所有权移交给 p1
执行完会有 p2.get() = 0x0
并且 p1 原本的 resource 会析构
copy ctor
Person p2 = p1; // copy constructor
p2 = p1; // copy assignment
标准的 copy ctor 和 copy assignment 写法:
class Person{
char *name;
};
void init(const char *s){
name = new char[strlen(s) + 1];
strcpy(name, s);
}
Person(const char *s){
init(s);
}
Person(const Person& other){
init(other.name);
}
Person& operator=(const Person& other){
if(this != &other){
delete[] name;
init(other.name);
}
return *this;
}
/**
* another copy assignment
*/
Person& operator=(const Person &other){
if(this != &other){
Person temp(other); // copy ctor
*this = std::move(temp);
}
return *this;
}
Person& operator=(Person &&other) noexcept = default;
第二种拷贝赋值更好
因为第一种如果在 init(other.name) 时出异常, 可能没有给 name 分配新资源, 对象进入无效状态
第二种如果在拷贝构造出异常, 那么临时对象出错不影响原对象, 并且不进行第二步, 向外抛出异常
如果第一步成功, 那么进入第二步
并且第二步的移动构造保证不出错, 确保异常安全性
之后赋值成功, 返回 *this
移动构造可以写成:
Person& operator=(Person&& other) noexcept {
if (this != &other) {
delete[] name;
name = other.name;
other.name = nullptr;
}
return *this;
}
这个过程保证不出错
return value optimization
返回值优化
Person foo(Person p){
return p; // copy ctor called
}
Person bar(const char *s){
return Person(s); // no copy ctor
}
int main(){
Person p1 = foo("Trump"); // 先拿 "Trump" 直接构造出 Person 对象, 然后拷贝构造给 p1
Person p2 = bar("Trump"); // bar 函数直接拿 const char* s 来构造 p2, 这是返回值优化
}
编译时加入 -fno-elide-constructors 会多出许多次拷贝构造
禁止拷贝构造
Person(const Person& other) = delete;
隐式转换
#include<iostream>
using namespace std;
struct One{
public:
int x;
};
struct Two{
public:
int y;
explicit Two(const One &one) : y(one.x) {}
};
void f(Two) {}
int main(){
One one;
f(Two(one)); // ok
f(one); // error
Two two1(one); // ok
Two two2 = one; // error
two1 = one; // error
return 0;
}
move ctor
std::move
将左值转换为右值
主要在移动赋值中起作用
使用它是为了和拷贝赋值作区分
在类对象的移动赋值中一般删除被移动的右值对象对资源的管理权:
Person& operator=(Person&& other) noexcept {
if (this != &other) {
delete[] name;
name = other.name;
other.name = nullptr; // release
}
return *this;
}
之后被移动对象为空
int a = 10;
int b = std::move(a); // no move ctor
cout << "a: " << a << endl; // 10
cout << "b: " << b << endl; // 10
string x = "hello";
string y = std::move(x); // have move ctor, and release x
cout << "x: " << x << endl; // null
cout << "y: " << y << endl; // hello
std::forward
完美转发, 保证右值不会被转换成左值
利用了引用折叠
T&& & = T&,T&& && = &&
即右值引用的左值引用是左值, 右值引用的右值引用是右值
operator
implicit conversion
member function:
struct Integer{
public:
int i;
Integer operator+(const Integer& other) const{
return Integer(i + other.i);
}
};
int main(){
Integer x, y;
x = x + y; // ok
x = x + 3; // ok
x = 3 + y; // error
return 0;
}
normal function:
struct Integer{
public:
int i;
friend Integer operator+(const Integer& lhs, const Integer& rhs);
};
Integer operator+(const Integer& lhs, const Integer& rhs) {
return Integer(lhs.i + rhs.i);
}
int main(){
Integer x, y;
x = x + y; // ok
x = x + 3; // ok
x = 3 + y; // ok, operator(Integer(3), y)
x = 3 + 7; // ok, Integer(10)
return 0;
}
++ and --
标准:
Integer& operator++(); // ++x;
Integer operator++(int); // x++;
++x; // x.operator()
x++; // x.operator++(0)
Integer& operator(){
++this.i;
return *this;
}
Integer operator(int){
Integer old(*this);
++(*this);
return old;
}
->* and .*
class A {
public:
A() {}
void foo() {
cout << "A::foo()\n";
}
};
int main(){
A a = A();
A *b = new A();
(a.*foo)();
(b->*foo)();
}
single argument implpicit conversion
class PathName {
string name;
public:
PathName(const string&);
~PathName();
};
string abc("abc");
PathName xyz(abc); // OK!
class PathName {
string name;
public:
explicit PathName(const string&);
~PathName();
};
string abc("abc");
PathName xyz(abc); // ERROR!
conversion operator
operator double() const{
return 1.0 * x;
}
必须作为 member function 的 operator
T& operator[](int index) { // allow modify
return elems[index];
}
T operator()(T x) const{
return x * 5; // operator* overloaded
}
operator->
operator= // copy assignment
不可 overload 的 operator
. .* :: ?:
sizeof typeid
static_cast dynamic_cast
const_cast reinterpret_cast
lambda function
#include<vector>
#include<iostream>
#include<functional>
using namespace std;
void transform(vector<int> &v, function<int(int)> f){
for(int& x : v)
x = f(x);
}
int main(){
vector<int> v = {1, 3, 5, 7, 9};
int a;
transform(v, [a](int x){return x * a;});
}
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
void transform(std::vector<int, std::allocator<int> > & v, std::function<int (int)> f)
{
{
std::vector<int, std::allocator<int> > & __range1 = v;
__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
int & x = __begin1.operator*();
x = f.operator()(x);
}
}
}
int main()
{
std::vector<int, std::allocator<int> > v = std::vector<int, std::allocator<int> >{std::initializer_list<int>{1, 3, 5, 7, 9}, std::allocator<int>()};
int a;
class __lambda_14_18
{
public:
inline /*constexpr */ int operator()(int x) const
{
return x * a;
}
private:
int a;
public:
// inline /*constexpr */ __lambda_14_18(const __lambda_14_18 &) noexcept = default;
// inline /*constexpr */ __lambda_14_18(__lambda_14_18 &&) noexcept = default;
__lambda_14_18(int & _a)
: a{_a}
{}
};
transform(v, std::function<int (int)>(__lambda_14_18{a}));
return 0;
}
相当于创建了一个类, 里面有变量 a; 同时定义了 operator()
capture list
传入的方式有按值传入和按引用传入
[this, &ofs](){}
这里传入了 this 指针和 std::ofstream& 引用
注意指针只需要按值传入即可
conversion operator
int x;
operator double() cosnt{
return (double)x;
}
对于 user-defined 类的转换 T -> C, 当且仅当一个条件满足:
C(T)在C中被定义operator C()在T中被定义
例如
struct C{
C() {}
C(T& x) {}
};
struct T{
operator C(){
return C();
}
};
void f(C o){}
int main(){
f(T()); // error
}
报错 ambigious
变成:
struct C{
C() {}
explicit C(T& x) {}
};
就不报错了
由于 ctor 经常出现, 所以建议写 explicit
stream
input operators
get(char *buf, int limit, char delim='\n')
Read up to limit characters, or to delim Appends a NULL character to buf Does NOT consume the delimiter
getline(char *b, int l, char d='\n')
Similar to above Does consume the delimiter
stream in class
class MyClass {
int value;
public:
MyClass(int v) : value(v) {}
std::ostream& operator<<(std::ostream& os) {
return os << value;
}
};
MyClass obj(42);
obj << std::cout;
顺序要反过来, 不自然
所以放在类外面写成友元函数
struct B{
int a;
string data;
B(int a, string data) : a(a), data(data) {}
ostream& operator <<(ostream& out){
return out << a << " " << data << endl;
}
friend ostream& operator <<(ostream&, const B&);
};
ostream& operator <<(ostream& out, const B& b){
return out << b.a << " " << b.data << endl;
}
int main(){
B b = B(114514, "hello world");
cout << b, b << cout;
return 0;
}
template
void foo(int a, int b = 0, int c); // illegal
c++ 参数从右往左给, 默认参数不能出现在中间
implicit conversion
#include <iostream>
#include <string>
using namespace std;
template <class T>
void print(T a, T b){
cout << "print() " << "a: " << a << ", b: " << b << endl;
}
void print(float a, float b){
cout << "print(float) " << "a: " << a << ", b: " << b << endl;
}
int main()
{
print(3.1f, 3.2f); // print(float)
print(3.1, 3.2); // print()
print(3, 3.2); // print(float)
return 0;
}
第一个, 程序优先调用普通函数
第二个是因为 3.1 默认 double, 所以调用模板
第三个是因为带推断模板不许隐式转换, 所以调用普通函数
conversion
从某一类型的模板类转换成另一类型的模板类
template<typename T>
struct A{
public:
T a;
A(T a) : a(a) {}
template<typename U>
A(const A<U> &other) : a(other.a) {}
};
int main(){
A<double> b = A<double>(21.0);
A<int> c(b);
cout << c.a;
return 0;
}
inheritance
普通类可以继承模板类, 模板类可以继承普通类, 模板类可以继承模板类
CRTP
struct Base{
void interface(){
static_cast<const T*>(this)->implementation(); // normal
}
static void static_func(){
T::static_func();
}
};
struct Derived: public Base<Derived> {
void implementation();
static void static_func();
};
优点: 对于报错, 用户能更快知道自己需要实现哪些接口
同时没有虚函数的开销
缺点: 对于在一个 vector 中存放不同类对象, 如果是用 CRTP, 由于模板参数, 这些类的基类不是同一个, 所以这个操作不可做
这时只能写一个公共基类, 然后写虚函数
template <typename T>
class Animal {
public:
void Speak() {
// 编译期绑定到具体派生类的实现
static_cast<T*>(this)->SpeakImpl();
}
};
class Cat : public Animal<Cat> {
public:
void SpeakImpl() { std::cout << "Meow!\n"; }
};
class Dog : public Animal<Dog> {
public:
void SpeakImpl() { std::cout << "Woof!\n"; }
};
// 使用
template <typename T>
void MakeSound(Animal<T>& animal) {
animal.Speak(); // 无虚表查找开销!
}
Cat c;
Dog d;
MakeSound(c); // 输出 "Meow!"
MakeSound(d); // 输出 "Woof!"
template <typename T>
class Builder {
public:
T& SetValue(int v) {
value = v;
return static_cast<T&>(*this);
}
protected:
int value;
};
class MyBuilder : public Builder<MyBuilder> {
public:
MyBuilder& Finalize() {
std::cout << "Finalized with value: " << value;
return *this;
}
};
// 链式调用
MyBuilder().SetValue(42).Finalize(); // 输出 "Finalized with value: 42"
why all in header file
因为实例化时需要看到源码, 所以所有有关模板类的代码要放到头文件里
example
一般的多文件:
// h.h
#ifndef __H_H__
#define __H_H__
#include <iostream>
template<class T>
void f(T t);
#endif
#include <iostream>
#include "h.h"
template<class T>
void f(T t){
std::cout << t << std::endl;
}
报错:
D:/TDM-GCC/bin/../lib/gcc/x86_64-w64-mingw32/10.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\yyr04\AppData\Local\Temp\ccVL5Cy2.o:a.cpp:(.text+0x15): undefined reference to `void f<int>(int)'
collect2.exe: error: ld returned 1 exit status
因为模板类定义要放在头文件里
duplicate symbol
如果在一个 .h 文件里写了一个函数, 在两个 .cpp 文件里调用, 同时头文件没有 #ifndef, 就会出现 duplicate symbol 问题
模板不会出现这个问题
模板与 inline 一样会加 weak definition, 在链接时允许多份存在, 最终去掉一份
iterator
value_type
当函数接口只给出迭代器, 我们想在里面用迭代器指向的类型:
template<class I>
// template<class I, class T>
void func(I iter){
T tmp = *iter; // T?
}
这时可以在迭代器内定义 value_type 用于推断类型
这样的属性有:
using value_type = T;
using reference = T&;
using pointer = T*;
using difference_type = ptrdiff_t;
using iterator_category = forward_iterator_tag; // #include <functional>
iterator_traits
对于裸指针, 也可以利用 iterator_traits 的模板特化将其包装成迭代器, 然后使用 value_type 等属性
template<class I>
class iterator_traits{
public:
using value_type = I::value_type;
using pointer = I::pointer;
};
template<class T>
class iterator_traits<T*>{
public:
using value_type = T;
using pointer = T*;
};
misc
universe reference
在需要推导的模板参数中 T&&
由于引用折叠:
T& & = T&
T&& & = T&
T& && = T&
T&& && = T&&
所以如果传入的是左值 X&, 那么 T&& = X& && = X&
如果是右值, 那么 T&& = X&& && = X&&
typename
在模板参数中, 如果有依赖于前面模板参数的嵌套从属名称, 那么就需要在这个表达式前加 typename, 告诉编译器这是个类型名称
template <class T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0>
void Serialize(const T &val, std::ofstream &ofs);
template <class T>
void foo(const T& t) {
typename T::iterator it; // T::iterator 是一个依赖于 T 的嵌套从属名称
}
但是如果用 using 给了一个别名, 编译器已经知道是类型名称, 就不需要再加 typename
template<class T>
using trivially_copyable = std::enable_if_t<std::is_trivially_copyable_v<T>, int>;
template <class T, trivially_copyable<T> N = 0>
void Serialize(const T &val, std::ofstream &ofs);
cast
static cast
一般类型的转换
如果转换的是指针, 类型之间不相容, 比如 int* 转 double*, 不相关的自定义类 A* 转 B* 会报错
int a = 10;
double b = static_cast<double>(a); // ok
double *c = static_cast<double*>(&a); // error
支持 upcast
struct U {
};
struct V : public U {};
struct W : public U {};
int main()
{
V v = V();
U u = static_cast<U>(v); // ok, upcast
V *v = new V();
U *u = static_cast<U*>(v); // ok, upcast
return 0;
}
struct U {
};
struct V : public U {};
struct W : public U {};
int main()
{
U u = U();
V v = static_cast<V>(u); // error
U *u = new U();
V *v = static_cast<V*>(u); // ok, downcast
return 0;
}
struct U {};
struct V : public U {};
struct W : public U {};
int main()
{
U * p = new U;
W * q = static_cast<W*>(p); // ok
U * p = new V;
W * q = static_cast<W*>(p); // ok
V * p = new V;
W * q = static_cast<W*>(p); // error
return q == nullptr;
}
reinterpret cast
二进制层面的转换, 只能转指针和引用
int 转 double, 不相关的自定义类 A 转 B 都可以, 但是不安全, 因为这些操作本身不合理
int a = 7;
double *p;
p = (double*) &a; // OK, but a is not double
p = static_cast<double*>(&a); // ERROR
p = reinterpret_cast<double*>(&a); // OK
这样 int 转 double* 会读取从 int 地址开始的 \(8\) 字节内容, 后 \(4\) 字节是未定义的
const cast
语义上负责常量与变量转换
dynamic cast
当类里有虚函数时, 可以用这个
即语义上负责多态之间转换
基类转派生类不行 (downcast), 派生类转基类可以 (upcast)
struct U {
};
struct V : public U {};
struct W : public U {};
int main()
{
V *v = new V();
U *u = dynamic_cast<U*>(v);
cout << (u == nullptr) << endl; // 0, ok
U *u = new U();
V *v = dynamic_cast<V*>(u);
cout << (v == nullptr) << endl; // 1, nullptr
return 0;
}
Vanilla MI
| IOS |
/ \
| istream | | ostream |
\ /
|iostream|
struct B1{int m_i;};
struct D1 : public B1{};
struct D2 : public B1{};
struct M : public D1, public D2{};
int main(){
M m;
B1 *p = &m; // ERROR, B1 appear twice
B1 *p = static_cast<D1*>(m); // OK
m.m_i++; // ERROR
}
虚继承, 防止继承两个
struct B1{int m_i;};
struct D1 : virtual public B1{};
struct D2 : virtual public B1{};
struct M : public D1, public D2{};
int main(){
M m;
B1 *p = new M; // OK
m.m_i++; // OK
}
namespace
可以跨文件定义
// #include "h1.h"
namespace X{void f();};
// #include "h2.h"
namespace X{void g();}; // X have f() and g()
可以特化已存在的函数
namespace lib{
class A{
// ...
};
void swap(A &a, A &b) noexcept {
// ...
}
};
namespace std{
template<>
void swap<lib::A>(lib::A &a, lib::A &b) noexcept{
swap(a, b);
}
};
ADL
当调用一个未限定的函数 \(f(x)\) 时:
- 普通作用域查找: 编译器首先在当前作用域 (如局部作用域, 全局作用域) 中查找函数 \(f\)
- ADL 查找: 如果未找到匹配的函数,编译器会进一步检查参数的类型(如 \(x\) 的类型), 并到参数类型的命名空间或类域中查找函数 \(f\)
上面没有指定 lib::swap(a, b), 但是 ADL 也根据参数类型 \(lib::A\) 找到了 lib::swap(a, b)
JNI
在 minisql 里, 可以把图书管理系统整合进来
具体地, 在 src 中加入 jni 模块:
// main_execute.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni/jni.h"
/* Header for class HelloJNI */
#ifndef _Included_MainJNI
#define _Included_MainJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MainJNI
* Method: HelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MainJNI_HelloWorld(JNIEnv *, jobject, jstring);
dberr_t execute(const char *cmd, vector<Row> &result_set, const Schema *&schema);
JNIEXPORT jstring JNICALL Java_MainJNI_Execute(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
// main_execute.cpp
#include <cstdio>
#include <sstream>
#include <string>
#include "executor/execute_engine.h"
#include "glog/logging.h"
#include "parser/syntax_tree_printer.h"
#include "utils/tree_file_mgr.h"
#include "jni/jni.h"
#include "jni/main_execute.h"
extern "C" {
int yyparse(void);
extern FILE *yyin;
#include "parser/minisql_lex.h"
#include "parser/parser.h"
}
ExecuteEngine engine;
JNIEXPORT void JNICALL Java_MainJNI_HelloWorld(JNIEnv *env, jobject thisObj, jstring Sth) {
const char* sth = env->GetStringUTFChars(Sth, nullptr);
cout << "Hello World from " << sth << endl;
// cout << &engine << endl;
return;
}
dberr_t execute(const char *cmd, vector<Row> &result_set, const Schema *&schema) {
// create buffer for sql input
// ExecuteEngine engine;
// for print syntax tree
TreeFileManagers syntax_tree_file_mgr("syntax_tree_");
uint32_t syntax_tree_id = 0;
YY_BUFFER_STATE bp = yy_scan_string(cmd);
if (bp == nullptr) {
LOG(ERROR) << "Failed to create yy buffer state." << std::endl;
exit(1);
}
yy_switch_to_buffer(bp);
// init parser module
MinisqlParserInit();
// parse
yyparse();
// parse result handle
if (MinisqlParserGetError()) {
// error
LOG(ERROR) << MinisqlParserGetErrorMessage() << endl;
} else {
// Comment them out if you don't need to debug the syntax tree
LOG(INFO) << "Sql syntax parse ok!" << endl;
SyntaxTreePrinter printer(MinisqlGetParserRootNode());
printer.PrintTree(syntax_tree_file_mgr[syntax_tree_id++]);
}
// vector<Row> result_set{};
auto result = engine.Execute(MinisqlGetParserRootNode(), result_set, schema);
// clean memory after parse
MinisqlParserFinish();
yy_delete_buffer(bp);
yylex_destroy();
engine.ExecuteInformation(result);
return result;
}
JNIEXPORT jstring JNICALL Java_MainJNI_Execute(JNIEnv *env, jobject thisObj, jstring Cmd) {
// dberr_t execute(const char *cmd) {
const char *cmd = env->GetStringUTFChars(Cmd, nullptr);
vector<Row> result_set{};
const Schema *schema = nullptr;
auto result = execute(cmd, result_set, schema);
string result_str = "";
if(!result_set.empty() && schema != nullptr){
stringstream ss;
ss << "{\n";
for(int j = 0; j < result_set.size(); j++){
ss << "\t\"" << "row" << j << "\": {\n";
Row &row = result_set[j];
for(int i = 0; i < schema->GetColumnCount(); i++){
Field *field = row.GetField(i);
ss << "\t\t\"" << schema->GetColumn(i)->GetName() << "\": \"" << field->toString() << "\"";
if(i != schema->GetColumnCount() - 1) ss << ",";
ss << "\n";
}
ss << "\t}";
if(j != result_set.size() - 1) ss << ",";
ss << "\n";
}
ss << "}";
result_str = ss.str();
}else{
result_str = "{\n}";
}
return env->NewStringUTF(result_str.c_str());
}
这样如果要正常使用 minisql, 只需在 main.cpp 里 #include "main_execute.h", 调用 execute() 即可
同时在主函数的 CMakeLists.txt 中:
# recursive files
FILE(GLOB_RECURSE MAIN_SOURCES ${PROJECT_SOURCE_DIR}/src/*/*.cpp
${PROJECT_SOURCE_DIR}/src/*/*/*.cpp
${PROJECT_SOURCE_DIR}/src/*/*/*/*.cpp
${PROJECT_SOURCE_DIR}/src/*/*.c
${PROJECT_SOURCE_DIR}/src/*/*/*.c
)
MESSAGE(STATUS "Source file lists: ${MAIN_SOURCES}")
ADD_LIBRARY(zSql SHARED ${MAIN_SOURCES})
TARGET_LINK_LIBRARIES(zSql glog)
ADD_EXECUTABLE(main main.cpp)
TARGET_LINK_LIBRARIES(main glog zSql)
所以 execute() 和 JNI 接口 Java_MainJNI_Execute() 都会被写入 libzSql.so 中
所以在图书管理系统中, 只需要新建一个 MainJNI.java:
public class MainJNI {
static {
// System.loadLibrary("zSql"); // Load native library zSql.dll (Windows) or libzSql.so (Unixes)
System.load("/mnt/d/db/new_minisql/library/libzSql.so");
}
public native void HelloWorld(String sth);
public native String Execute(String cmd);
private static final Logger log = Logger.getLogger(Main.class.getName());
public static void main(String[] args) throws IOException {
// 这里是8000,建议不要80端口,容易和其他的撞
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
// 添加handler,这里就绑定到/card路由
// 所以localhost:8000/card是会有handler来处理
server.createContext("/book", new BookHandler());
server.createContext("/card", new CardHandler());
server.createContext("/borrow", new BorrowHandler());
// 启动服务器
server.start();
// 标识一下,这样才知道我的后端启动了(确信
System.out.println("Server is listening on port 8000");
}
}
最后编译时使用它为主类即可
mvn exec:java -Dexec.mainClass="MainJNI" -Dexec.cleanupDaemonThreads=false
在 LibraryManagementSystemImpl.java 里把 connector 改成 private final MainJNI connector;
就可以使用 connector.Execute() 来操作数据库
Macro
#define DECLARE_BINARY_SERIALIZABLE(...) \
private: \
static constexpr auto members = []() { \
return std::make_tuple(__VA_ARGS__); \
}(); \
public: \
using SERIALIZABLE = void; \
void Serialize(std::ofstream &ofs) const { \
std::apply([this, &ofs](auto&&... args) { \
(binary_serialization::Serialize(this->*args, ofs), ...); \
}, members); \
} \
void serialize(const char *filename) { \
std::ofstream ofs(filename, std::ios::out); \
if (!ofs.is_open()) \
{ \
std::cerr << "failed to open file " << filename << std::endl; \
return; \
} \
this->Serialize(ofs); \
ofs.close(); \
} \
void Deserialize(std::ifstream &ifs) { \
std::apply([this, &ifs](auto&&... args) { \
(binary_serialization::Deserialize(this->*args, ifs), ...); \
}, members); \
} \
void deserialize(const char *filename) { \
std::ifstream ifs(filename, std::ios::in); \
if (!ifs.is_open()) \
{ \
std::cerr << "failed to open file " << filename << std::endl; \
return; \
} \
this->Deserialize(ifs); \
ifs.close(); \
}
这段宏定义是在写序列化函数时, 为了方便序列化自定义类的
在自定义类中:
struct UserDefinedType {
int idx;
std::string name;
std::vector<double> data;
DECLARE_BINARY_SERIALIZABLE(&UserDefinedType::idx, &UserDefinedType::name, &UserDefinedType::data)
UserDefinedType() = default;
UserDefinedType(int idx, std::string name, std::vector<double> data) : idx(idx), name(name), data(data) {}
};
就会加入上面宏的内容
第一个 members 函数使用折叠表达式, 接受一系列参数, 将他们, 即 __VA_ARGS__, 传入 std::tuple 中
在下面的 Serialize() 函数中, 用 std::apply() 将 members() 得到的 std::tuple 传入前面的折叠表达式做参数
member pointer
成员指针
存储成员变量在类中的偏移量
class MyClass {
public:
int value;
void display() { cout << value << endl; }
};
int MyClass::*ptr = &MyClass::value;
MyClass obj;
obj.value = 42;
std::cout << obj.*ptr << std::endl; // 42
MyClass* pObj = &obj;
std::cout << pObj->*ptr << std::endl; // 42
void (MyClass::*funcPtr)(int) = &MyClass::display;
(obj.*funcPtr)(42); // 42
所以要在有对象的前提下使用
fold expressions
C++17 新特性
template<typename ... Args>
void call(Args&&... args){
(... , args())
}
可以用逗号表达式, 后面写出具体做的事 (可以用括号括起来几件事), 前面的 ... 会把后面表达式展开
template deduct and sepcialization / SFINAE
可以用下面方式产生一个接受参数 T 并返回对错的模板:
template<class, class = void>
struct v : std::false_type {};
template<class T>
struct v<T, __expression__> : std::true_type {};
其中 __expression__ 是一个可以产生 void 类型的表达式
可以用下面方式:
std::void_t<typename A::a, typename B::b, ...>;
std::enable_if_t<true / false, void>
在 std::void_t<> 中, 如果 A::a 和 B::b 存在, 则表达式成功产生类型 void
在 std::enable_if_t 中, 如果前面表达式为 true, 那么后面的 void 会被启用
这利用了 SFINAE (Substitution Failure Is Not An Error)
所以可以用 v<T>::value 来返回一个 true / false
编译参数
-Werror=unused-variable: 未使用的变量视为错误, 方便去掉无用的变量
lock
mutex and condition variable
使用 std::unique_lock<std::mutex> lock(mtx) 对 mtx 加锁
如果另一个线程 \(B\) 通过相同方式加锁, 会等待这个线程 \(A\) 离开作用域释放锁, 才能加锁
或者通过条件变量让 \(B\) 等待某个条件
使用 cv.wait(std::unique_lock<std::mutex> &, bool)
后面就是表达式
等待时自动释放锁, 因为尝试加锁, 等待就是先释放掉, 等别人唤醒了自己在继续尝试加锁
可以避免一直轮询互斥锁, 减少 CPU 占用
当且仅当表达式满足时才能加锁
或者使用 使用 cv.wait(std::unique_lock<std::mutex> &), 等待别人调用 cv.notify_all() 唤醒自己