c++ notes

vector扩容缩容问题

两倍缩容,申请新空间,copy元素,释放老空间。

shrink_to_fit() 释放多出来的空间

扩容申请新空间后,所有原迭代器都会失效

    cout << test.capacity() << endl; // 0
    test.push_back(1);
    cout << test.capacity() << endl; // 1
    test.push_back(1);
    cout << test.capacity() << endl; // 2
    test.push_back(1);
    cout << test.capacity() << endl; // 4
    test.resize(8);
    cout << test.capacity() << endl; // 8
    test.resize(1025);
    cout << test.capacity() << endl; // 1025
    test.resize(1026);
    cout << test.capacity() << endl; // 2050
    test.resize(1);
    cout << test.capacity() << endl; //2050
    test.shrink_to_fit();
    cout << test.capacity() << endl; //1

template 的实现放在头文件中

https://blog.csdn.net/imred/article/details/80261632

skiplist

https://my.oschina.net/ljc94/blog/807388

柔性数组

typedef struct pen
{
    int len;
    char data[];//柔性数组
}pen;

// sizeof(pen) == 4

C99使用不完整类型实现柔性数组成员,在C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组(flexible array)成员(也叫伸缩性数组成员),但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。柔性数组成员只作为一个符号地址存在,而且必须是结构体的最后一个成员,sizeof 返回的这种结构大小不包括柔性数组的内存

unique_lock和lock_guard的区别

unique_lock和lock_guard最大的不同是unique_lock不需要始终拥有关联的mutex,而lock_guard始终拥有mutex。这意味着unique_lock需要利用owns_lock()判断是否拥有mutex。另外,如果要结合使用条件变量,应该使用unique_lock。

varint

用每个字节得第1位作为最高有效位。0表示当前字节为最后一个字节。 leveldb和protobu等都用到了

Encode : | 0xF0,移位

Decode : & 0x7F,移位

char* EncodeVarint64(char* dst, uint64_t v) {
  static const int B = 128;
  uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
  while (v >= B) {
    *(ptr++) = v | B;
    v >>= 7;
  }
  *(ptr++) = static_cast<uint8_t>(v);
  return reinterpret_cast<char*>(ptr);
}

void PutVarint64(std::string* dst, uint64_t v) {
  char buf[10];
  char* ptr = EncodeVarint64(buf, v);
  dst->append(buf, ptr - buf);
}

const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
  uint64_t result = 0;
  for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
    uint64_t byte = *(reinterpret_cast<const uint8_t*>(p));
    p++;
    if (byte & 128) {
      // More bytes are present
      result |= ((byte & 127) << shift);
    } else {
      result |= (byte << shift);
      *value = result;
      return reinterpret_cast<const char*>(p);
    }
  }
  return nullptr;
}


bool GetVarint64(Slice* input, uint64_t* value) {
  const char* p = input->data();
  const char* limit = p + input->size();
  const char* q = GetVarint64Ptr(p, limit, value);
  if (q == nullptr) {
    return false;
  } else {
    *input = Slice(q, limit - q);
    return true;
  }
}

读写锁如何避免写锁饥饿

std::mutex mutex_;
std::shared_ptr<data> G_data(new data());

void read(){
	std::shared_ptr<data> Temp;
	{
		std::lock_guard<std::mutex> guard(mutex_);
		Temp = G_data;
	}
	Temp->read_something();
}

void write(){
    std::lock_guard<std::mutex> guard(mutex_);
    if(!G_data.unique()){
        G_data.reset(new data(*G_data));
    }
    G_data->write_something();
}

读线程访问时,写线程可以去更新保护数据的副本,但写线程需要等待所有读线程完成读取后,才可以删除老对象

Wall Clock and Monotonic Clock

Wall clock(time)就是我们一般意义上的时间,就像墙上钟所指示的时间。

Monotonic clock(time)字面意思是单调时间,实际上它指的是从某个点开始后(比如系统启动以后)流逝的时间,jiffies一定是单调递增的!

序列化和反序列化

把对象转化为可传输的字节序列过程称为序列化

把字节序列还原为对象的过程称为反序列化

address sanitizer

https://www.jianshu.com/p/3a2df9b7c353

https://github.com/google/sanitizers/wiki/AddressSanitizer

gcc编译时加上 -fsanitize=address 选项

自顶向下VS自底向上

  • 自顶向下:从抽象到具体
  • 自底向上:从具体到抽象

个人理解:以造房子为例。自顶向下:现设想好房子大概长什么样,画好轮廓,再搭好柱子,最后再搭地基。自底向上:还没想好盖成啥样,但搭好地基准没错,一边搭一边想。

自顶向下设计,自底向上实现

lower_bound和upper_bound

孩子总是记不住这两个的区别

lower_bound:下界,大于等于 [
upper_bound:上界,大于 (

if you have multiple elements in the range [first, last) whose value equals the value val you are searching for, then the range [l, u) where

l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)
is precisely the range of elements equal to val within the range [first, last). So l and u are the "lower bound" and "upper bound" for the equal range. It makes sense if you're accustomed to thinking in terms of half-open intervals.

程序局部性原理

为什么快排比堆排和归并快?为什么插入比选择和冒泡快?

程序局部性原理:是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域,具体来说,局部性通常有两种形式:时间局部性和空间局部性。

时间局部性:被引用过一次的存储器位置在未来会被多次引用(通常在循环中)。

空间局部性:如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。

右值引用 折叠引用

左值和右值

右值引用。c++11标准提供右值引用。

A a1 = GetA();   // a1是左值
A&& a2 = GetA(); // a2是右值引用

a1是左值,在构造时使用了GetA() 产生的临时对象,之后GetA()产生的临时对象会销毁。

a2是右值引用,其指向的就是GetA()所产生的对象,这个对象的声明周期是和a2的声明周期是一致的。即少了临时对象,从而省去了临时对象的构造和析构。

纯右值引用折叠后才是右值引用,只要有一个左值引用,就是左值引用。

A& & 变成 A&
A& && 变成 A&
A&& & 变成 A&
A&& && 变成 A&&

move

移动构造函数。

类不会生成移动构造函数,在传入右值引用时,如果未定义则匹配左值引用,即复制构造函数

std::move 将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。

移动构造函数和移动赋值函数定义方法如下

Thread(Thread &&t);
Thread &operator=(Thread &&t);

forward 完美转发

不管是T&&、左值引用、右值引用,std::forward都会按照原来的类型完美转发

remove_reference

线程池

ThreadPool.cc中,start函数确定了初始启动线程数量(固定,不会自动扩容缩容)

双端队列queue_用于存储task,notEmpty_条件变量用来通知线程池执行task,notFull_通知线程池可以继续添加任务。

std::bind

在bind中,如果要传递一个引用需要使用std::ref,常量引用为std::cref

using

构造函数的 using 声明
在 C++11 中,派生类能够重用其直接基类定义的构造函数。

class Derived : Base {
public:
    using Base::Base;
    /* ... */
};

如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:

Derived(parms) : Base(args) { }

decltype

decltype 关键字用于检查实体的声明类型或表达式的类型及值分类。语法:

decltype ( expression )

decltype 使用

// 尾置返回允许我们在参数列表之后声明返回类型
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    // 处理序列
    return *beg;    // 返回序列中一个元素的引用
}
// 为了使用模板参数成员,必须用 typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
    // 处理序列
    return *beg;    // 返回序列中一个元素的拷贝
}

initializer_list 列表初始化

用花括号初始化器列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数.

initializer_list 使用

#include <iostream>
#include <vector>
#include <initializer_list>
 
template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // 在 return 语句中复制列表初始化
                                   // 这不使用 std::initializer_list
    }
};
 
template <typename T>
void templated_fn(T) {}
 
int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // 复制初始化
    s.append({6, 7, 8});      // 函数调用中的列表初始化
 
    std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";
 
    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';
 
    std::cout << "Range-for over brace-init-list: \n";
 
    for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作
        std::cout << x << ' ';
    std::cout << '\n';
 
    auto al = {10, 11, 12};   // auto 的特殊规则
 
    std::cout << "The list bound to auto has size() = " << al.size() << '\n';
 
//    templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,
                             // 它无类型,故 T 无法推导
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // 也 OK
}

如何定义一个只能在堆上(栈上)生成对象的类?

只能在堆上

方法:将析构函数设置为私有

原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。

只能在栈上

方法:将 new 和 delete 重载为私有

原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。

智能指针

#include <memory>

shared_ptr

多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。

支持定制型删除器(custom deleter),可防范 Cross-DLL 问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁

weak_ptr

weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 default 和 copy 构造函数之外,weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。

可打破环状引用(cycles of references,两个其实已经没有被使用的对象彼此互指,使之看似还在 “被使用” 的状态)的问题

unique_ptr

unique_ptr 是 C++11 才开始提供的类型,是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有着被销毁或编程 empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。

unique_ptr 用于取代 auto_ptr

强制类型转换运算符

static_cast
dynamic_cast
const_cast
reinterpret_cast

运行时类型信息 (RTTI)

dynamic_cast

用于多态类型的转换

typeid

typeid 运算符允许在运行时确定对象的类型

type_id 返回一个 type_info 对象的引用

如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数
只能获取对象的实际类型

type_info

type_info 类描述编译器在程序中生成的类型信息。 此类的对象可以有效存储指向类型的名称的指针。 type_info 类还可存储适合比较两个类型是否相等或比较其排列顺序的编码值。 类型的编码规则和排列顺序是未指定的,并且可能因程序而异。

头文件:typeinfo

typeid、type_info 使用

#include <iostream>
using namespace std;

class Flyable                       // 能飞的
{
public:
    virtual void takeoff() = 0;     // 起飞
    virtual void land() = 0;        // 降落
};
class Bird : public Flyable         // 鸟
{
public:
    void foraging() {...}           // 觅食
    virtual void takeoff() {...}
    virtual void land() {...}
    virtual ~Bird(){}
};
class Plane : public Flyable        // 飞机
{
public:
    void carry() {...}              // 运输
    virtual void takeoff() {...}
    virtual void land() {...}
};

class type_info
{
public:
    const char* name() const;
    bool operator == (const type_info & rhs) const;
    bool operator != (const type_info & rhs) const;
    int before(const type_info & rhs) const;
    virtual ~type_info();
private:
    ...
};

void doSomething(Flyable *obj)                 // 做些事情
{
    obj->takeoff();
    
    // 输出传入对象类型("class Bird" or "class Plane")
    cout << typeid(*obj).name() << endl;        

    if(typeid(*obj) == typeid(Bird))            // 判断对象类型
    {
        Bird *bird = dynamic_cast<Bird *>(obj); // 对象转化
        bird->foraging();
    }

    obj->land();
}

int main(){
	Bird *b = new Bird();
	doSomething(b);
	delete b;
	b = nullptr;
	return 0;
}

lambda表达式

捕获

any

boost::any (c++17中被引入标准库)

相比于void*增加了对类型的判断,更安全

any可以存储任意的类型,并能通过any_cast将any类型转化成原本的类型

实现:存储一个placeholder指针(智能指针?),利用template、多态、typeid等来存储不同类型的变量。any_cast是any的友元函数,可以进行指针转换,失败返回nullptr。也可以进行值转换,失败抛出bad_any_cast异常

std::async

shared_ptr 和 weak_ptr的问题

shared_ptr的循环引用可能导致对象无法销毁,导致内存问题。weak_ptr不会增加引用计数,可以解决这个问题。

下面的代码,A和B的对象都不会释放。不会看到析构函数的输出

class B;
class A
{
public:
    ~A() { cout << "~A" << endl; }
    shared_ptr<B> mp;
};

class B
{
public:
    ~B() { cout << "~B" << endl; }
    shared_ptr<A> mp;
};

int main()
{
    auto pb = std::make_shared<B>();
    auto pa = std::make_shared<A>();
    pa->mp = pb;
    pb->mp = pa;
    return 0;
}

用valgrind可以检测出内存泄漏

ubuntu@VM-0-10-ubuntu:~/github/notebook $ valgrind ./demo
==24914== Memcheck, a memory error detector
==24914== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==24914== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==24914== Command: ./demo
==24914== 
==24914== HEAP SUMMARY:
==24914==     in use at exit: 64 bytes in 2 blocks
==24914==   total heap usage: 3 allocs, 1 frees, 72,768 bytes allocated
==24914== 
==24914== LEAK SUMMARY:
==24914==    definitely lost: 32 bytes in 1 blocks
==24914==    indirectly lost: 32 bytes in 1 blocks
==24914==      possibly lost: 0 bytes in 0 blocks
==24914==    still reachable: 0 bytes in 0 blocks
==24914==         suppressed: 0 bytes in 0 blocks
==24914== Rerun with --leak-check=full to see details of leaked memory
==24914== 
==24914== For counts of detected and suppressed errors, rerun with: -v
==24914== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

改用weak_ptr之后不存在这个问题

placement new

new delete 分为两步:申请空间&调用构造/ 调用析构&释放空间

operator new :只申请空间
operaotr delete: 只释放空间

placement new : operator new 的一个重载版本,new(ptr) T在给定的地址空间new对象,进行构造。对象析构的时候显示调用析构函数

可变参模板

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

namespace detail
{
template <typename T, typename... Args>
inline void __construct(T* p, const Args&... args)
{
    std::cout << sizeof...(args) << std::endl;
}
} // namespace detail

template <typename T, typename... Args>
inline void construct(T* p, const Args&... args)
{
    detail::__construct(p, args...);
}

gflags调整姿势

才发现之前gflags一直用的不对,有资源泄漏问题

int main(int argc, char** argv)
{
    testing::InitGoogleTest(&argc, argv);
    google::ParseCommandLineFlags(&argc, &argv, true);
    google::ShutDownCommandLineFlags();
    return RUN_ALL_TESTS();
}

调用ParseCommandLineFlags后需调用ShutDownCommandLineFlags释放内存。

template sfinae初探

判断一个类是否有to_string成员函数,有的话调用

template <typename T>
struct has_to_string
{
    template <typename C>
    static char test(decltype(&C::to_string));
    template <typename C>
    static int32_t test(...);
    const static bool value = sizeof(test<T>(0)) == sizeof(char);
};


template <typename T>
typename std::enable_if<has_to_string<T>::value, std::string>::type
GetString(const T& value)
{
    return value.to_string();
}

template <typename T>
typename std::enable_if<!has_to_string<T>::value, std::string>::type
GetString(const T& value)
{
    return std::to_string(value);
}

// 另一种写法
template <typename T>
std::string
GetString(const T& value, typename std::enable_if<has_to_string<T>::value, void*>::type = nullptr)
{
    return value.to_string();
}

template <typename T>
std::string
GetString(const T& value, typename std::enable_if<!has_to_string<T>::value, void*>::type = nullptr)
{
    return std::to_string(value);
}

std::forward

函数的参数有多个,每个参数的引用类型都可能不同,如何处理呢?必然不能重载所有可能性,所以需要用到完美转发,std::forward来传递

https://blog.csdn.net/Stranger_CJHan/article/details/69830916

元数据

元数据是用来描述数据的数据(Data that describes other data)

compare

c++ - 参数相等时std::sort compare函数必须返回false

© 皖ICP备20011981号