Antkillerfarm Hacking V7.0

C/C++编程心得(二)

2018-03-04

C++ 11/14/17/20的新特性(续)

6.using,extern

//before
struct Derive : public Base{
    Derive(int a,char b,double c, std::string d,float f):Base(a,b,c,d,f){}
};

//after
struct Derive : public Base{
    using Base::Base;
};

当子类中的成员函数和基类同名时,子类中重定义的成员函数将隐藏基类中的版本,即使函数原型不同也是如此。

如果要取消这种隐藏,需要显式使用using。

http://blog.csdn.net/cnsword/article/details/8034947

C++11老关键字的新含义(auto, using,extern)

https://www.cnblogs.com/wishchin/p/9199835.html

C++11:using的各种作用

动态链接

这两天实践了一下怎样在linux下创建动态链接。感觉网上的资料虽然翔实,但仍然有疏漏之处。

1)g++和gcc的区别

本来只想给链接的,以显示这不是我的原创。但是现在的链接失效的也太快了。。。囧

只好拿华丽的分隔符来表示引用的内容。


误区一:gcc只能编译c代码,g++只能编译c++代码

两者都可以,但是请注意:

1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。

2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

误区二:gcc不会定义__cplusplus宏,而g++会

实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

误区三:编译只能用gcc,链接只能用g++

原因基本同第一条。此外,g++基本等同gcc -lstdc++。


libstdc++是GCC搞的,是g++默认的stdlib链接库。

libc++是LLVM搞的,是clang++默认的stdlib链接库。


因此,某些时候编不过去,可以试试换换cc的值。

2)gcc4.1.1下似乎对类型检查严了一些,dlsym返回的void*类型不能转换为相应的函数指针类型,需要强制转换。某些网上的例子在这里编不过去。

3)显式调用时,要注意动态库函数的声明,可能要加extern "C"才能正常执行。(显式调用是运行时加载,所以编译能过,执行却不对了。)可以用nm命令看看链接库的符号表,以确定问题所在。


so最大的麻烦来自于,它的链接是顺序相关的。比如一个可执行文件,如果依赖于a.so和b.so,而a.so又依赖于b.so,那么链接的时候就要写-Wl a.so b.so。如果写反了,就会找不到符号。

https://www.zhihu.com/question/320400918

动态库是像Windows那样两个文件(.lib/.dll)好,还是Linux那样合二为一(.so)好?

惰性求值

使用延迟求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在后面的某个时候求值。

http://www.cnblogs.com/gtarcoder/p/4811614.html

c++11实现l延迟调用(惰性求值)

http://www.fuzihao.org/blog/2016/02/10/C-%E5%AE%9E%E7%8E%B0%E6%83%B0%E6%80%A7%E6%B1%82%E5%80%BC/

C++实现惰性求值

左值和右值

左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;

右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。

一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。

左值语法:type &引用名 = 左值表达式;

右值语法:type &&引用名 = 右值表达式;

C++11通过引入右值引用来优化性能,具体来说是通过move语义来将临时生成的左值中的资源无代价的转移到另外一个对象中去。即对象处于需要拷贝,且被拷贝之后,不再被需要的场景下。

但右值引入也带来的新问题。

原来我们只有:

template<typename T> void f1(T& t);

现在还有:

template<typename T> void f2(T&& t);

不加限制的话,还会有T&& &(将T&&作为参数传给f1),所以就有了引用折叠(Reference collapsing)的概念,即:

& & => &
&& & => &
& && => &
&& && => &&

如果把上述规则看作logical-OR的话,那么&就是1,而&&就是0。

注意,因为临时变量是右值,所以T&&的类型在f2函数体内实际上就是T

std::move:无条件的转为右值引用。

std::forward:有条件的转为右值引用。

std::remove_reference:将T&T&&变成T

如果要强制对象只能move,不能复制的话,可以这样做:

A& operator=(const A& other) = delete;
A& operator=(A&& other);

参考:

https://www.cnblogs.com/ldlchina/p/6608154.html

C++11右值引用和std::move语句实例解析

https://www.jianshu.com/p/b90d1091a4ff

C++11 std::move和std::forward

http://shaoyuan1943.github.io/2016/03/26/explain-move-forward/

详解C++11中移动语义(std::move)和完美转发(std::forward)

https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c/

Perfect forwarding and universal references in C++

https://mp.weixin.qq.com/s/tkbnoH3XQW50ceaYKyK8Fg

再谈C++右值引用与完美转发

RAII

资源获取即初始化(Resource Acquisition Is Initialization),或称RAII,是一种C++编程技术,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期绑定与一个对象的生存期相绑定。

C++标准库遵循RAII管理其自身的资源:类在构造函数中获取其资源(错误时抛出异常),并在其析构函数中释放之(决不抛出),而不要求显式清理。

RAII不适用于并非在使用前请求的资源:CPU时间、核心,以及缓存容量、熵池容量、网络带宽、电力消费、栈内存等。

RAII也是下面介绍的各类智能指针的理论基础。

参考:

https://mp.weixin.qq.com/s/m44zVWMvactsLPjKsFnlSg

RAII惯用法:C++资源管理的利器

GC in C++

https://www.hboehm.info/gc/

A garbage collector for C and C++(boehm gc)

https://blog.codingnow.com/2008/06/gc.html

引用计数与垃圾收集之比较

https://www.cnblogs.com/duguguiyu/archive/2007/11/14/959178.html

GC in C++

各类ptr

auto_ptr:它允许程序员创建一个指向某种资源的指针对象,当该对象离开它的作用域时,它所指向的资源也会被自动释放。

在原本的C++中,new和delete必须配对使用,然而给每个异常处理分支添加delete是一件很麻烦的事。auto_ptr就是用来干这事的,它无需显式调用delete。

对auto_ptr的赋值和拷贝会导致原来的auto_ptr变为NULL,从而无效化。

unique_ptr:auto_ptr是全局唯一的,且不采用引用计数管理,因此,赋值和拷贝都是不必要的,且会造成混淆。(尤其拷贝会导致原来的auto_ptr变为NULL,这个还能叫做拷贝吗?)因此,auto_ptr在后续标准中,不再推荐使用,而是用unique_ptr替代。unique_ptr默认不支持赋值和拷贝,确需使用,要与std::move配合方可。否则会编译出错。

unique_ptr早先在boost库中的时候,也叫做scoped_ptr。

shared_ptr:为多个拥有者管理内存中对象的生命周期而设计的。在你初始化一个shared_ptr后,你可以复制它,把函数参数的值递给它,并把它分配给其它shared_ptr实例。所有实例指向同一个对象,并共享访问一个“控制块”,即每当一个新的shared_ptr被添加时,递增和递减引用计数,超出范围,则复位。当引用计数到达零时,控制块删除内存资源和自身。

weak_ptr:如果对象A中有对象B的shared_ptr,而对象B中又有对象A的shared_ptr,那么就会出现循环引用的情况。这时可以使用weak_ptr。两者的区别在于新建shared_ptr会增加引用计数,而weak_ptr不会。

上述指针还可以自己定义deleter:

void close_file(std::FILE* fp) { std::fclose(fp); }
std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt", "r"),&close_file);

上面的close_file就是自定义的deleter,用来进行特殊的析构。

上述ptr不仅可以保存对象的指针,还可以保存数组指针:

shared_ptr<double[]> p2( new double[n] );

此外ptr的声明方式也有两种(以unique_ptr为例):

  • 方法1:std::unique_ptr<int>(new int(1));

  • 方法2:std::make_unique<int>(1);

从实现来看,这些智能指针除了需要new对象之外,还需要new一个用于维护对象引用计数的控制块,因此第2种方法一次性分配的效率会比较高。

一般来说,make_XXX在标准库中,通常都带有new对象和相关控制结构的操作,除了上述的make_unique之外,类似的还有make_pair等。


shared_ptr既然号称共享,那自然有一定的共享规则在:

shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2(sp1), sp3;
sp3 = sp1;
//一个典型的错误用法
shared_ptr<int> sp4(sp1.get()); 
cout << sp1.use_count() << " " << sp2.use_count() << " " 
<< sp3.use_count() << " " << sp4.use_count() << endl;
//输出 3 3 3 1

sp1,sp2,sp3是相互关联的共享指针,共同控制所指内存的生存期,sp4虽然指向同样的内存,却是与sp1,sp2,sp3独立的,sp4按自己的引用计数来关联内存的释放。

auto p = std::make_unique<XXX>();
void f(XXX*);
f(p); // error
f(p.get()); // OK

虽然,类似p->YYY()的用法和普通指针一致,但是作为f函数参数的时候,就不行了。


多线程的weak_ptr需要使用enable_shared_from_this来持有shared_ptr。使用时,用weak_ptr的lock函数返回shared_ptr,如果shared_ptr为null,则表明该对象已经被析构了。


参考:

https://mp.weixin.qq.com/s/32aeGOPaySjTmyKcjFKgYA

窥见C++11智能指针

https://www.cnblogs.com/wangkeqin/p/9351191.html

C++内存管理之shared_ptr

https://blog.csdn.net/River_Lethe/article/details/78734879

shared_ptr的使用和陷阱

https://blog.csdn.net/qq_38410730/article/details/105903979

weak_ptr弱引用智能指针详解

Fork me on GitHub