Skip to content

STL

    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));
    copy(l.begin(), l.end(), ostream_iterator<int>(cout, ", "));

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()
*/

memory

#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

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
}
struct X{
    const int i; // must be initialized in Ctor
    X() : i(0) {

    }
}
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 可以让变量 / 函数与类相关, 不与对象绑定

inline void f(int n){
    return pow(n, 2);
}
int main(){
    cout << f(2) << endl;
}

汇编:

call f;
变成:
call pow;

所以把里面内容展开了

再类的声明里面的函数自动都是 inline

static

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 存放在外面, 只有一份, 所有对象共享

inline

类里的成员函数默认是 inline, 允许重定义

在类外面定义, 同时要在两个文件中引用这个头文件, 就要加 inline 防止重定义报错

virtual

虚指针 vptr 在截断赋值时不会被处理

copy constructor

返回值优化

struct Person{
    Person(const char * s){
        // ...
    }
    Person(const Person& other){
        // ...
    }
    Person bar(const char * s){
        return Person(s);
    }
};
int main(){

    Person b = bar("a"); 
    return 0;
}

这里的 bar() 函数返回值赋值给 b 不会有拷贝构造

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

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
    Preson 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

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
    x = 3 + 7; // ok
    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;
}

必须作为 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

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;});
}
cppinsights.io 查看:
#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, 当且仅当一个条件满足:

  1. C(T)C 中被定义
  2. 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

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{
    int a;
    template<typename U>
    A(const A<U> &other) : a(other.a) {}
};

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, 由于模板参数, 这些类的基类不是同一个, 所以这个操作不可做

这时只能写一个公共基类, 然后写虚函数

why all in header file

因为实例化时需要看到源码, 所以所有有关模板类的代码要放到头文件里

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 T>
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

一般类型的转换

如果转换的类型之间不相容, 比如 intdouble, 不相关的自定义类 AB 会报错

reinterpret cast

二进制层面的转换

intdouble, 不相关的自定义类 AB 都可以, 但是不安全, 因为这些操作本身不合理

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

这样 intdouble* 会读取从 int 地址开始的 \(8\) 字节内容, 后 \(4\) 字节是未定义的

const cast

语义上负责常量与变量转换

dynamic cast

当类里有虚函数时, 可以用这个

即语义上负责多态之间转换

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)\) 时:

  1. 普通作用域查找: 编译器首先在当前作用域 (如局部作用域, 全局作用域) 中查找函数 \(f\)
  2. 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::aB::b 存在, 则表达式成功产生类型 void

std::enable_if_t 中, 如果前面表达式为 true, 那么后面的 void 会被启用

这利用了 SFINAE (Substitution Failure Is Not An Error)

所以可以用 v<T>::value 来返回一个 true / false

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() 唤醒自己