C++ 学习记录
程序员文章站
2022-07-09 21:53:28
...
2018.10.11
- 学习了序列化,反序列化的概念
2018.10.10
- 经过他群里的讨论,了解了一个 gcc 黑科技:struct { int len; char data[0]; }; 很奇特!在这里 data 不包含数据,不占用空间,只是一个数组名字而已,如果需要分配,则在struct 紧跟着的连续空间分配。
- 复习了 std::function , lambda , bind ,tuple
- 学习了群主博客中的组合函数(链式调用) Compose(f, g, h, i).run(args)
2018.10.9
- 学习了 lazy 延迟创建的模板类,和我之前学的代理模式作用有些类似,不过这个可以容纳所有返回类型,所有参数类型的对象,而且代码简洁(当然书写的简洁,代码还是可能膨胀)。
启示:
std::function 函数(回调)机制可以延迟实现!用在 io 中就是“异步”。- 了解了 std::signals2 的用法,其涉及到 connection ,类似 Qt 中的信号槽机制,而且 connection 还可断开 disconnect,使用比较灵活
- 学习 std::hash 的使用2
- 学习了如何模仿 python 中的 range,也算是学习了模板的应用,
(1)如何应对多个参数的功能?重载!
(2)如何统一对象 ?不可能每种功能一种对象吧——提供默认参数。
(3)同时学习了内部 iterator 的原理,对容器来说,需要重载 begin(), end();对迭代器来说,要访问就要重载 operator*,要自增自减就要重载 operator++,operator-- ,其中还涉及到 const 的使用, iterator 含外部容器类引用等细节。
(4)如何使用自定义 range 容器?针对不同参数,返回不同的容器对象,向外提供 range 借口,在借口中统一参数类型。
(5)not_eq 不可重载,其默认调用 !=- 学习了群主对命令模式的改进,“解决了”命令爆炸的情况(不是真的解决,只是用模板极大地简化了书写)。经典模式就是 command.wrap(reciever, &function)。下午自己实现了下 command 的静态写法。遇到了两个问题:
(1)auto it = holder.front(); holder.pop_front() 在容器中 pop 出去的内存是不存在了的,我猜想是容器内部实现的,在 pop_front() 之后再引用 it 就会导致程序崩溃。mark !
(2)类型匹配时,出现 no matching funciton call 对应代码template<typename Class, typename... Class_Args, class Obj, typename... Args> void wrap(result(Class::*fun)(Class_Args...), Obj&& obj, Args&&... args)
我的错误就在 result(Obj::fun) ,导致 no matching function ,类函数的地址和对象函数的地址是不一样的概念,所以调用的时候 wrap( &classname,…) 而不应该是 &Obj…。
我学到了什么?
(1)加深了对命令模式的理解。设计模式并不一定局限于面对对象程序设计,也可以用模板实现(CRTP就是最好的例子)
(2)学习了模板的书写
(3)复习了对象内部成员指针的用法 ::, 学习了如何包装成员函数(以往我包装函数都是局限于 non-member_funtion- 学习了群主对于 逗号表达式 的应用,可以在逗号前展开可变参数,也可以在逗号前 decltype 推断类型。具体参考 链接
- 顺带了解了链式调用的概念。。。。最近老碰到 C# ,这个语言优点还蛮多的。
2018.10.8
- 学习了 makefile 的基本内容,“参考书”——跟我一起写makefile
日后这个网站,只当 C++ primer 一般使用,需要用时再查询- 学习了 boost.lexical_cast 的实现
- 找到了群主的 github 江南
- 自己实现了简易 lexical_cast
- 学习了 optional 的简单使用,和模拟实现。典型的应用情景是函数调用时,如需根据条件返回一个对象(有效)或默认对象(无效),若该对象构造成本很高(资源分配等),可用optional返回一个空对象,提高效率。参考 std::optional
注意:
(1)就地创建:
optional要求类型T具有拷贝语义,因为它内部会保存值的拷贝,但很多时候复杂对象的拷贝代价很高,而且这个值仅仅作为拷贝的临时用途,是一种浪费。因此optional库提供出了"就地创建"的概念,可以不要求类型具有拷贝语义,直接用构造函数所需的参数创建对象,这导致发展处了另一个Boost库–in_place_factory.
(2)延迟创建,促进 std::lazy 的实现。判返回的数据是否有效
(3)参考 如何使用 std::optional
绝佳的理解 std::optional 的例子
std::optional<std::string> UI::FindUserNick()
{
if (nick_available)
return { mStrNickName };
return std::nullopt; // same as return { };
}
// use:
std::optional<std::string> UserNick = UI->FindUserNick();
if (UserNick)
Show(*UserNick);
启发:有时,含参数模板,返回值可以返回空的 return std::null_opt 一样的值,减少构造
6. 顺带在 C++ 参考上学习了 optional 和 advance, std::pair<T, bool>, next. prev, std::refrence_wrapper
7. 学习了 Lazy 延迟创建的技术
2018.10.7
- 今天学习了 boost::any 的源码,收获良多。
(1)使用万能的中间层!将模板参数延迟到 any 内部的模板派生类,any 只需要持有一个基类指针即可做到类型擦除 !很棒!
(2)boost::any 很注重异常安全,多使用 swap
(3)读源码的过程中遇到了跨平台,跨编译器的一些说明,例如 BOOST_NO_RVALUEREFERENCE 等,参考博客 boost 源码中的一些说明
(4)其实在测试的过程中也发现了 any 也不是万能的,因为只是存储上任意了,取出来时还是需要判断类型的,any_cast 就需要模板参数,而且 any_cast 比较复杂,我至今没看明白所有。- 自己粗略实现了 any
遇到不少问题:
(1)is a inaccessable base of … 基类无法访问到派生类——很别扭,后来在 * 发现问题处在 public 继承上。如果是 private 继承,外部无法看到 derived 的 base 部分。
(2)因为编译器的warning, 学习了“关键方法”的定义,例如,虚表会定义在关键方法(第一个虚函数)被定义的源文件中,作为共享;但如果 inline 在 .h 文件,极其容易被扩散到 include 该 .h 的所有文件中, 会增加连接器的处理时间。
参考博客:好文章- 遇到了派生类虚函数覆盖基类同名不同签名的问题——采用 using 生命,有时还可 private
- 发现自己的 singleton 可变参数模板还是有缺陷的——如果一个对象有几种构造函数,单例模式只会调用其中一个,而且只有一次。我的目标是——针对每种构造函数,控制单例模式!代码已实现。
遇到了一些问题:
(1)刚开始想通过 boost::any 存储不同的构造方法,例如 std::functionstd::shared_ptr > 之类的,但是发现,不好取出来
(2)借鉴超级对象池的实现,通过 typeid 手段,根据构造方法的名字,映射到某一单例,成功
(3)还考虑到一个问题,我想到了对象池,回收单例,说不定可以提升性能 ?经过一番探索,我明白没有这个必要——首先,单例本身需求小,而这样的单例模式 + 对象池略显复杂,在实际中说不定还会损耗性能;第二点,只要在 map 中存有单例的引用,该单例就一直生存到程序结束,不需要考虑回收和名字同步问题。
2018.10.6
- 学习了超级对象池的原理。不过自己尚未实现自定义删除器无法删除,以及第二次使用回收过后的对象。
- 学习了万能函数包装器的概念,以及完美语义转发,左右值引用。&& 如果被左值初始化,会变成左值引用,
- 重新编译了 boost 库 sudo ./b2 install
- 在博客good的帮助下,成功编译了 boost.asio,命令是 g++ try.cpp -lboost_system -I/usr/local/include/boost -L/usr/local/lib -o try -lboost_thread -lpthread
2018.10.5
- 通过 CRTP 实现了静态织入的 AOP
- C++ 中的类型擦除概念——
(1)继承机制,基类为派生类提供统一的借口,性能上有缺陷
(2)模板机制,但有类型必须指定的限制
(3)boost.variant 可容纳不同类型的对象,但必须是事先存在的类型
(4)boost.any 可以容纳任何类型,但是取出时还是需要指定类型
(5)闭包 lambda 表达式,std::function<> 可以代表一个操作,从而擦除对象类型- 再次学习了 ScopeGuard,修正了转移构造函数的缺点,还加上了线程安全控制(std::mutex) ,加上了泛型的
template < typanme funType> ,借鉴了 Loki 源码中的做法,把 ScopeGuard 构造函数设为private, 向外提供 makeGuard 借口。最后,完善了可变参数的 ScopeGuard 。大致如下:
namespace YHL {
template<typename funType>
class scopeGuard final : boost::noncopyable {
private:
funType onExitScope;
bool dismissed;
std::mutex mtx;
explicit scopeGuard(funType _onExitScope, funType acquire = []{})
: onExitScope(_onExitScope), dismissed(false)
{ acquire(); }
public:
~scopeGuard() noexcept {
std::lock_guard<std::mutex> lck(mtx);
if(dismissed == false) {
dismissed = true;
onExitScope();
}
}
// 转移构造
scopeGuard(scopeGuard&& other)
: onExitScope(other.onExitScope),
dismissed(other.dismissed) {
other.Dismiss (true);
}
// 转移操作符
scopeGuard& operator=(scopeGuard&& other) {
if( this not_eq &other ) {
this->onExitScope = other.onExitScope;
this->dismissed = other.dismissed;
other.dismissed = true;
}
return *this;
}
void Dismiss(const bool _dismissed) {
std::lock_guard<std::mutex> lck(mtx);
dismissed = _dismissed;
}
static scopeGuard<funType> makeGuard(funType _onExitScope, funType acquire = []{}) {
return scopeGuard<funType>(_onExitScope, acquire);
}
};
/*
template <typename funType>
inline scopeGuard0<funType> makeGuard(funType onExitScope, funType acquire = []{}) {
return scopeGuard0<funType>::makeGuard (onExitScope, acquire);
}*/
template <typename funType, typename... Args>
inline scopeGuard<std::function<void()> > makeGuard(funType&& onExitScope, Args&&... args) {
return scopeGuard<std::function<void()> >::makeGuard([&]{
onExitScope(std::forward<Args>(args)...);
});
}
}
缺点,书写麻烦,因为模板参数的原因,暂未实现宏定义
2018.10.4
- 把代码上传到 github
(1)git add main.cpp
(2)git status
(3)git add ./
(4)git status
(5)git commit -m “try”
(6)git remote add origin https://github.com/FluenceYHL/Try-How-to-git.git
(7)git push -u origin branch- 重写了一遍线程池,对 conditional_variable 以及 mutex 的认识加深。mutex 互斥量在同一时间内保证只有一把锁,其他线程无法获取这把锁。锁依靠 Acquire 和 Release 语义构成了临界区,保护了共享内容。例如,在线程池中,锁的作用是保护任务 task 队列(不能同时多个线程访问)和一些控制变量。
- 用面向对象的思想包装了 pthread ,实现了建议的 C++ std::mutex 和 std::lock_guard。
- 学习了对象池的实现。
- 学习了如何生成自己的 .a, .lib 静态库文件
- 学习了单例模式 + 可变参数
- 学习了加强版的 mapSingleton ,可以控制单例的数量
- 学习了注册版工厂模式
- 复习了 AOP 面向切面编程的概念,编码了 AOP 的简易实现
- 学习了 std::enable_shared_from_this 和 shared_from_this 的用法和易错点。不可以
return shared_ptr<this>
, 只能 继承自 std::enable_shared_from_this ,自带 shared_from_this()。
2018.10.3
- 编译运行一个 C++ 程序的流程:
(1)g++ -std=c++14 promise.cpp -lpthread -o promise
(2)./promise- Ubuntu 16.04 配置 boost
(1)sudo apt-cache search boost
(2)sudo apt-get install libboost-dev
(3)g++ -std=c++14 untitled.cpp -o untitled -lphread -lboost_system
(4)./untitled- Ubuntu 16.04 boost.asio
(1)参考 编译链接选项
(2)boost.asio 编译选项
(3)boost.asio 入门编译选项- 学习了std::future, std::packaged_task, std::promise, std::async 的用法。
(1)future 顾名思义,线程未来的共享状态
(2)promise 包装了 future,可以储存一个值
(3)packaged_tack 包装了 future,可以存储可调用对象
(4)async 包装了以上三个和 std::thread,不需要再创建线程,一般最好用 std::async 创建线程。std::async先将异步操作用std::packaged_task包装起来,然后将异步操作的结果放到std::promise中,这个过程就是创造未来的过程;外面再通过future.get/wait来获取这个未来的结果。- 了解了 lock_guard,unique_lock,mutex(四种)
- 再次学习了线程池:线程池的关键是——一个线程池 + 任务队列
每个线程都不断轮询任务队列,如果有可以执行的任务,就把任务放到线程中。- Ubuntu 16.04 配置 log4cpp 日志库
(1)sourseforge 中下载 1.1.2版本 log4cpp 1.1.2
(2)进入下载目录
(3)./configure (最好./configure --with-pthreads)
(4)make
(5)make check
(6)make install
(7)sudo subl /etc/profile.d/log4cpp.sh 用 sublime 打开之后添加以下两行LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export LD_LIBRARY_PATH
(8)chmod a+x /etc/profile.d/log4cpp.sh 增加文件的可执行权限
参考 https://blog.csdn.net/mugua250/article/details/7831279
补充:在 Qt creator 工程 pro 中加入以下内容
INCLUDEPATH += /usr/local/include
LIBS += /usr/local/lib/liblog4cpp.a
LIBS += /usr/local/lib/liblog4cpp.so
#LIBS += /usr/local/lib/liblog4cpp.la
LIBS += /usr/local/lib/liblog4cpp.so.5
LIBS += /usr/local/lib/liblog4cpp.so.5.0.6
- Ubuntu 16.04 安装配置 opencv 链接
注意点:一定要是最新版的
在 Qt creator pro 编译选项中要加入以下内容:
INCLUDEPATH += /usr/local/include \ /usr/local/include/opencv \ /usr/local/include/opencv2 LIBS += /usr/local/lib/libopencv_highgui.so \ /usr/local/lib/libopencv_core.so \ /usr/local/lib/libopencv_imgproc.so \ /usr/local/lib/libopencv_imgcodecs.so
- Ubuntu 16.04 Qtcreator Pro文件配置 boost
INCLUDEPATH += /usr/include/boost
INCLUDEPATH += /usr/local/include/boost
LIBS += /usr/local/lib/libboost_timer.a
LIBS += /usr/local/lib/libboost_system.a
LIBS += /usr/local/lib/libboost_thread.a
LIBS += /usr/local/lib/libboost_math_c99.a
LIBS += /usr/local/lib/libboost_atomic.a
LIBS += /usr/local/lib/libboost_chrono.a
LIBS += /usr/local/lib/libboost_container.a
LIBS += /usr/local/lib/libboost_context.a
LIBS += /usr/local/lib/libboost_contract.a
LIBS += /usr/local/lib/libboost_coroutine.a
LIBS += /usr/local/lib/libboost_date_time.a
LIBS += /usr/local/lib/libboost_exception.a
LIBS += /usr/local/lib/libboost_filesystem.a
LIBS += /usr/local/lib/libboost_graph.a
LIBS += /usr/local/lib/libboost_iostreams.a
LIBS += /usr/local/lib/libboost_locale.a
LIBS += /usr/local/lib/libboost_log.a
LIBS += /usr/local/lib/libboost_math_c99l.a
LIBS += /usr/local/lib/libboost_math_tr1.a
LIBS += /usr/local/lib/libboost_math_tr1f.a
LIBS += /usr/local/lib/libboost_math_tr1l.a
LIBS += /usr/local/lib/libboost_python27.a
LIBS += /usr/local/lib/libboost_random.a
LIBS += /usr/local/lib/libboost_regex.a
LIBS += /usr/local/lib/libboost_serialization.a
LIBS += /usr/local/lib/libboost_signals.a
2018.10.2
- 学习了线程池的来源和应用场景——考虑,如果服务器需要处理数目巨大的请求,如果采取“即时创建,即时销毁”的策略,那么服务器会处于不断创建和销毁的状态下,尤其是当服务本身所占时间短的情况下,创建和销毁的部分将会不可忽略;有时还需要考虑服务器瓶颈,线程池可以限制最多同时访问的线程数目。
实现:
(1)采用预创建的方法,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
缺点:需要考虑同步的开销;因为线程池需要占用一定的空间,如果线程本身创建和销毁所占时间很短的话,线程池就略显不足了。
适用场景(1)线程创建和开销所展开销较大的情况下(2) 实时性要求高,线程池不需要即时创建。- 初步了解了 std::promise, std::future 和 std::package_task, std::synic 的基本用法。
- 学习了”锁“的内部原理。参考 并发基础: 锁
关键两点(1)锁构建了临界区,同一时间内只有一个线程可以进入临界区(2)锁 lock 的 Acquire 语义(插入的限制,类似内存屏障)保证了临界区以下的代码不会被优化至 lock 之前;unlock 的 Release 语义,保证了unlock 以上的临界区代码不会被延迟至unlock 之后执行。- 顺带复习了内存屏障的概念——主要分为编译器内存屏障(没有实际代码)和 CPU 内存屏障(存在实际指令)
- 学习了 volatile 的三种特点(1). 易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取(2)“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行(3)顺序性,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化;但和 非 volatile 变量之间可能被编译器交换顺序。
了解了 Java 的 volatile 是加强的。
了解了 Happen Before
了解了 volatile 的起源——和外设之间的数据交换。
参考 C++ volatile- 学习了 spinlock 的机制和易错点。参考 深入理解 spinlock
- 学习了 atomic 的本质。参考atomic 原子变量
- 浏览了如何构建个人网站
- 学习了虚拟主机的概念
- 了解了 Debian
- 在 linux 下 -S 输出汇编,观察了简单程序的几个步骤
- 在类 linux 下对 C++ 线程的操作需要加上 -lpthread 编译链接选项。可执行文件不可以加 .exe(这个是 windows 下)
- 之前一直不懂 do{} while(0) 的意义,学习了
(1)#define 宏定义中局部作用域
(2)取代 {} 段,而且可以随时跳出 break
2018.8.15
- 拜读了文章 技术人员如何创业,值得反思,技术并不是创业的全部,
- 学习了内存对齐的原理:
原因:
(1) 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
(2) 性能原因:经过内存对齐后,CPU的内存访问速度大大提升。
CPU 并不是一个一个字节看内存的,而是1 , 2 , 4 , 8 , 16 个字节来看(作为一小块),以块作为读写单位。[ 0 , 4 ]中选 4 个字节 和 [ 1 , 7 ] 中间选 4 个字节,前者 CPU 只要访问一次内存,后者要访问两次,而且容易出现内存碎片。
参考博客 :
(1)内存对齐的原理
(2)计算内存对齐- 见识了返回对象的 static 变量中的大陷阱 !因为 static 变量只有一份,前者容易被后者覆盖。参考博客 static 陷阱
- 复习了 C++ 内存的分布和虚函数原理。
解决了三个困惑
(1)为什么虚函数一定要借助指针 ?比如Base o = Derived example
,是不能实现多态的,一直卡在子类 Derived 的内存布局上,原因其实在 Base ,Base o = Derived example 隐式类型转换之后,得到的是 Derived 内存布局中的前段,所以只是一个 Base 对象而已,通过这个 Base对象调用的只是 Base 的函数。所以要区分开基类和子类的内存布局,子类虚表中存在替换;但基类虚表不存在替换,还是原来的基类虚表 。
指针可以实现多态的原因:指针获取的地址 + 偏移量 。实际得到的地址是基类对象地址,调用的就是基类的函数;实际得到的是子类地址,调用的是子类的函数。这里的地址,实际上就是虚表地址。
多个对象继承时,还存在类型转换和地址转移的过程。
参考博客:图解 C++ 虚函数
(2)同一个类,使用的是同一张虚表(因为类定义是一样的),和同样的 RTTI (type_info)机制。
留下了几个问题:
(1)Base o = Derived example
这里,Base 得到的是 Derived 内存的前段部分(属于 Base 定义的),那么虚表呢? Base 的虚表按理应该还是 Derived 才对,难道这里还有虚表地址转换吗?
(2)没有虚函数的类有虚表吗?如果没有虚表,那类如何调用成员函数(this 指针访问静态存储区,总需要一张表的吧)
(3)虚表储存在哪儿?静态存储区?还是堆区?还是栈区?或者寄存器?我的猜想:静态存储区。
(4)如果没有对象实体,虚表还存在吗?- 学习了
va_start
,va_end
,va_arg
的用法,可以得到参数列表
2018.8.14
- 不要
delete void*
指针(内置类型除外),原因是void*
分配的内存是没有类型的,只是一块内存而已,不存在析构函数。如果void *ptr = new example
再delete ptr ; ptr = nullptr ;
会忘记调用析构函数,容易造成内存泄漏。
参考博客:不要 delete void *
关于void*
的介绍参考 void 指针- 学习了类型萃取技术(类似于 Java 中的反射)
初步入门:浅显易懂
中级入门:类型萃取的高级应用
技巧 :模板做设计时,一般建议在模板定义内部,为模板的每个类型参数提供typedef定义,这样在泛型代码中可以很容易地访问或抽取这些类型
2018.8.11
- 再次了解了 RAII 的基本原理和使用,例如文件流和对付死锁时的简单应用。
- 初步了解了 RAII 工厂,其实原理就和原来我写的工厂模式中的垃圾收集器一样,只不过,工厂只负责单一职能,分层更好。
- 了解了
scopeGuard
的原理和基本使用
2018.8.2
- 初步了解了 RAII 的概念和在单例模式中的应用。
参考博客 C++ RAII 的设计技巧 和 RTII 的分析RAII ( Resource Acquistion Is Initialization )
,C++ 语言中一种资源管理,避免资源泄露的方法。一个对象在其构造时获取资源,在生命周期内控制对资源的有效访问,最后析构的时候释放资源。RAII
的优点:不用显式释放资源,将资源用类封装起来,资源操作都封装在类的内部;可以做到异常安全,如果程序发生异常,造成提前退出,如果没释放资源就会造成资源泄露,而 RAII 就可以解决这个问题。
本质: 将资源的获取和释放与对象的构造和析构对应起来。
在单例模式中的应用:内部类的自动清理,如下:
class Litter {
public:
~Litter() {
cout << "\n清理单例类" << endl ;
if ( null not_eq nullptr )
delete null ;
null = nullptr ;
}
} ;
// GC 清理类
static Litter litter ;
- 复习了 final 在防止继承的用法
class Entity final {} ;
虚拟继承在单例模式Lock<Entity>
中的作用:派生类会尝试直接调用 Lock 的构造函数;如果不是虚拟继承,派生类会调用 Entity 的构造函数,Entity 会调用 Lock 的构造函数。
2018.8.1
- 接触了
std::thread
的基本用法,构造,含参数构造,获取 CPU 核心,sleep_for
和std::chrono
和std::this_thread
的使用join
和detach
。在使用前最好判断if joinable
,因为一个线程只能 join 或 detach 一次。
区别:join
会阻塞主线程,知道当前线程执行完毕,再 join 会合,再清理线程资源detach
线程会和线程对象分离,对象可以销毁,但是线程依旧在运行std::mutex
加锁的使用- 原子变量
atomic_int
表示最原子的操作,不允许任何操作打断直到原子操作结束,效率比加锁高。
自定义实现atomic_t
原子变量(失败)- 继承自 thread 的类可以拓展功能,例如
system( str )
,- 封装
thread
实现 RAII,可以实现异常安全,及时释放资源(失败)- 线程可以交换,不可以赋值
- 线程可以移动,但是原来的会销毁
2018.5.18
- 今天,才明白——要重载,
operator new
和operator delete
真是大神能做的。一个项目,我修改了文件的包含关系,程序编译成功了,但是程序一运行就崩溃了!经过多次调试输出,才发现,程序崩溃在main
函数之前——所以,问题出在main
函数之前,经过一番学习,猜测是静态变量和全局变量的初始化,用到了…new
,然后我把这些operator new
和operator delete
注释之后,程序正常 !
修改文件之前,没有崩溃,应该和文件的包含关系有关,谁在前,谁在后——也说明了一点,静态变量之间最好不要有牵连!我这次项目,牵连就出在——内存检测的容器,初始化不一定在operator new
和operator delete
之前,所以,容器可能还不存在!
学习了!static constexpr char* password = "Fluence_YHL" ;
问题是:warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings] static constexpr char* password = "Fluence_YHL" ; 改为:
static constexpr char* password = (char*)“Fluence_YHL” ;` 就行了
2018.5.15
_alloca
函数,C 的库函数,允许在栈区动态分配空间,而且申请空间之后会自动释放——栈区也可以动态分配
不过_alloca
函数是不安全的。_msize ()
函数,C 库函数,可以得出malloc
为指针分配了多少空间。
2018.5.14
- 今天遇到了
constexpr
关键字,C++11 新特性
极好的参考博客 Effective Modern C++ 条款15 尽可能使用constexpr
和const
的对比学习:
(1).constexpr
修饰的对象一定是const
的,但是const
修饰的对象不一定是constexpr
的。
(2).constexpr
修饰的变量在编译时就有确定的值,编译器会确保这一点,但是const
修饰的变量在编译期不一定要用已知的值初始化。
例子:int a = 10 ;
const int b = a ;
std::array<int , b > // 错误
如果想要编译器保证变量编译期有值,即上下文请求了一个编译期间的常量,那么能用的工具是constexpr
,而不是const
(3).constexpr
函数在传入编译期已知值作为参数时,会在编译期间生成结果;如果编译期得不出结果,constexpr
修饰的函数就和普通的函数是一样的。
C++ 14 允许constexpr
函数内部有局部变量,和if
,switch
条件选择语句。
1. 2018.5.12
- 尝试菱形继承,发现根类会构造,析构两次
学习了虚继承 virtual public [基类 classname]
极好的参考博客:从内存布局看C++虚继承的实现原理- 学习了一个技巧——g++ 命令查看虚表内容( 例 empty(19).cpp )
g++ -std=c++11 -fdump-class-hierarchy empty(19).cpp
get 了神技能,可以观察到 vTable 的分布和对象的布局- 学习了
typeid
关键字的使用和表面原理typeid
在对象是否具有虚函数时是动态判断的typeid
在判断指针和指针的解引用时的区别
参考博客 typeid
进一步学习:
通过观察 vTable 布局,可以发现,主虚表和次虚表的前面都含有一个一样的RTTI
的类型信息,子类和基类的不一样。正是利用这一点,运行时,typeid
通过vptr
找到虚函数表,虚函数表前面的那个位置存储了类型信息对象,编译器会为每一个不同的类生成唯一标识该类型的类型信息对象,typeid
就是返回这个对象的引用。dynamic_cast
的动态原理和typeid
类似。
但是,dynamic_cast
会略显复杂,在继承关系中,RTTI
对象还会保存其父类的RTTI
对象的指针,呈树状结构,有了继承关系,再递归从虚表中查找基类子对象在派生类中的偏移值,便可以确定最终返回地址。- 今天通过菱形的虚继承,引出来了很多关于 C++ 内存布局的问题,尤其是虚表结构,拜读了某位博主的一系列博客
C/C++杂记:虚函数的实现的基本原理
C/C++杂记:深入虚表结构
C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
C/C++杂记:深入理解数据成员指针、函数成员指针
茅塞顿开:
运行时多态(虚函数机制),基类指针获取的vptr 虚表指针
是确定的,至于运行时调用哪个函数,还是不确定的,按根据偏移量来寻找真实调用的函数。
如果基类指针指向的是基类对象,指针获取的是基类对象的vptr
, 指向的的虚表当然全部是基类的函数了,运行时只会找到基类的函数
如果基类指针指向的是子类对象,指针获取的是子类对象的vptr
, 指向的虚表就是子类的虚表,其中可能有从基类继承
过来的虚函数,也可能有重写基类
的虚函数,还可能有子类自己声明的虚函数( 子类独有的非虚函数
的不在虚表中 ),程序运行时真实调用的函数还不清楚,因为还不清楚子类vptr
中基类的虚函数是否会被子类重写。vptr
只是一个类似索引
的东西。
为什么虚函数机制不是编译时多态?
因为编译时,编译器只知道基类指针是基类类型的指针,但不知道基类指针指向的到底是基类对象还是子类对象,只有到运行时,找到真正的对象,看实际内存中最靠前
的虚表指针vptr
,这时候已经没有了基类和子类的概念,因为机器只知道按照vptr
和偏移量
去寻找要调用的函数,找到谁就执行谁。
我想这也就是为什么虚表指针vptr
要放在对象的最前面。- 多继承,或者是菱形等复杂继承时,会出现
主虚表
和次虚表
的分类,子类和其中一个基类共享一个vptr
,节约空间。
C/C++杂记:虚函数的实现的基本原理- 遇到了
cin.ignore ()
函数,顺便初步了解了一下cin.get ()
,cin.sync ()
,cin.clear ()
- 初步了解了策略模式
参考博客:《JAVA与模式》之策略模式
我还是不能理解,为什么策略模式可以解决if else
过多和switch
的问题。大致了解了一下,策略模式就像是一台机器,具备了针对同一种行为的多种策略——比如我要实现一个老师,学生,管理员不同登录方式的系统,就可以采用策略模式。
初步实现了上述的登录demo
- 留下的问题:
typeid
和RTTI
,以及vptr
的关系还不是很明确dynamic_cast
和RTTI
的关系try
异常捕获和RTTI
的关系
内存对齐是什么
尝试自己实现一个智能指针smart_ptr<T>
在赋值操作上出问题
2018.5.9
- 在
imlk
的指点下,大致明白了策略模式的优势所在。
如何避免了if else
或者switch
?for ( auto &it : user ) it.call_strategy () ;
当有多个对象,进行同一个操作,具体操作不同时,就可以忽略判断,直接调用策略!
如果就一个对象调用策略,就体现不出优势。- 在测试策略模式的多对象同时操作时,我掉进了一个
浅拷贝
的坑。再次体会到了vector
的不安全和深拷贝复制函数
的重要性。
上一篇: java Servlet笔记18
下一篇: C数组学习记录