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;});
}
#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
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
一般类型的转换
如果转换的类型之间不相容, 比如 int
转 double
, 不相关的自定义类 A
转 B
会报错
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
当类里有虚函数时, 可以用这个
即语义上负责多态之间转换
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
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()
唤醒自己