C++中没有placement delete

placement new即带有额外参数的new表达式: T *p = new(string{"hi"}) T;。该表达式会调用相应的operator new来获得内存地址,然后再上面调用构造函数。那个额外的参数一般用来指定内存地址或内存分配器,少数情况下用来追踪。

比较奇怪的是竟然没有对应的placement delete,即 delete(string{"end"}) ptr; 这样的代码是不合法的。C++之父给出的答案是C++的类型系统无法推断与ptr相关联的参数,容易被误用,如果需要的话可以手工构造一个destory函数来模拟。但我觉得容易被误用这个理由很牵强,因为毕竟需要显式手工调用,而且placement delete显然比destory函数含义更明显,也更方便。

但这样也有一个好处:可以把应该在placement delete中实现的逻辑放在普通的operator delete中,这样就能统一通过 delete ptr; 来删除,也方便了智能指针类的实现。

虽然没有placement delete,但与placement new 的operator new对应的operator delete还是要定义的,它会在构造函数抛出异常时自动调用,避免内存泄露。

另外operator delete不能声明为虚函数,但仍能在通过基类指针删除子类对象时调用正确的operator delete,原因在于operator delete函数是在析构函数内部调用的,只要析构函数是虚函数operator delete就能正确调用。

还意外发现一点:子类析构函数调用父类析构函数前,对象的虚表指针会先被修正为指向父类的虚表。因此调用一次子类对象的析构函数后,再次调用析构函数会发现,它只会调用父类的析构函数了。

为什么C++中很少使用内部类?

C++和Java都支持内部类,但C++却鲜见有内部类的使用。仔细想了一下,原因应该与模板有关。

在以上代码中,print()的定义是没有问题的,但友元operator+的定义却无法通过编译,原因在于模板函数的类型推断不适用于作用域,即无法自动推导出::左部的模板参数。要想通过编译只能像被注释掉的那样定义成内联的。

标准库中的迭代器类定义成容器类的内部类再适合不过了。但标准库的实现中迭代器类是定义在容器类的外部的,通过typedef把迭代器类型声明在了容器类中,可能就是因为以上原因(迭代器一般都需要友元函数operator+)。

C、C++、Java、Python、JavaScript比较

从实际使用感受来比较一下这些语言。

C vs C++03

C语言只提供了编程所必须的基本语法。除了类和模板,C++还添加了一些方便开发者的语法。C++使代码更加模块化,开发者效率更高。以下是我觉得C语言最为严重的缺陷。

  1. 命名空间
    在C中所有函数名都是全局的,为了避免冲突,C的函数名经常又臭又长。static只能解决以文件为单位的问题,而实际中变量名的隔离都是以功能为单位的。
  2. 函数重载
    在C中经常会看到一系列与类型相关的函数,比如CJson库:addIntToObject、addBoolToObject、addStringToObject,调用这类函数经常让人抓狂。
  3. 对象
    在C中,经常会出现要操作某个变量,却忘记了相应函数名的情况,这时只能翻文档去找。
    C++中对象成员的自动补全解决了这个问题。同时将变量与函数放在一起,也使代码结构更加清晰。
  4. 异常
    在需要对输入做安全检测时,经常会出现多级if嵌套来处理错误,为了避免多级嵌套,C中只能通过使用goto语句来解决。
    C++通过异常将错误处理逻辑与正常执行逻辑分离,使代码结构更加清晰。

C++11 vs Python

C++11引入auto、for循环、Lambda、using等常用语法后,在模板和异步编程方面的易用性大为提高,Python的语法糖优势已经不那么明显了。

而且C++11的部分编程风格现在与Python很接近,比如C++11中用begin(),end()全局函数来迭代,这与Python的全局函数len(),str()的设计很相似。

现在Python相对C++11的主要优势为大量的第三方库(开发速度的提升)、跨平台部署。

对于规模不大的程序或没有太多时间的个人开发者而言,Python是首选。

C++ vs Java

Java一开始是以跨平台作为最大亮点的,但现在Java跨平台主要体现在开发方面:Windows上开发,Linux上部署。

Java对C++的优势主要在反射和自醒能力,方便了框架开发。由于Web服务的主要瓶颈在IO而不在运行效率,所以Java成为了Web开发方面的霸主。

因为Java的易学易用,Java的第三方库也比C++丰富和成熟。

Java vs Python

现在Java的业务和Python业务大部分都是重合的:Web、爬虫、数据处理。

与C++恰恰相反,Python对Java的优势主要是在语法方面。

Javascript vs Python

JavaSciprt虽然与Java没关系,但是创建对象要用new的语法还是和Java很像。

ECMAScript 6引入了部分Python中的语法后,除了块级作用域和类继承方面的不同外,JavaScript的使用已经和Python很相仿了。

应用场景

C语言最大的优势是运行速度,所以它主要用于系统开发、嵌入式开发、底层应用开发(Nginx,Apache)。  在上层应用方面,只有搞开源的那帮人才使用。

C++兼顾运行速度和抽象能力,但开发速度慢,对开发人员要求高。主要用于大规模软件的开发:office、adobe全家桶、autocad、gcc、clang。(git这个软件,一直觉得最应该用C++开发的,但是开发者死守C语言)。另外C++虽然各方面的库也不少,但要么不成熟设计得很难用,要么年久失修有bug,经常被迫选择C库。

Java:Web(SSH、JSF),安卓(这个是特例,几乎没有软件拿Java来做GUI)

Python:爬虫、Web、小程序

总结

现在语言的改进都倾向于通过自我演化来实现,而不是另造一门语言。比如C++标准的频繁更新,Python语法的不断变化。所以目前语言的格局已经很难改变了。

失败是因为在错误的路上走得太远

失败是因为在错误的路上走得太远。这句话看似简单,游戏中也经常有人用“走远了”来戏谑,但其实并不简单。

首先,正如牛顿定律被相对论取代一样,有些理论在一开始其实是正确的,只是环境的变化导致了正误的逆转。所以错误并没有那么容易识别,有谁能预测相对论什么时候会不成立呢?

其次,走得太远有时也不是因为固执,而是因为没有及时对自己认为正确的观点和理论进行质疑和论证。但说起来容易做起来难,人的精力都是有限的,不可能经常性地去怀疑自己。利用集体的力量能够缓解这个问题,但集体也会抹杀个体的超前预测导致走向中庸,而且集体失明的现象也时有发生。

再者,多远算远?这个取决于具体事情和对手,也不易把控。

所以有时即便很努力,准备也很充分,但仍然会失败。而对于失败的原因,人们也都是后知后觉。

这就是成功的等式里永远会有 %1的运气的原因。

SNI技术的妙用

SNI即服务器名称指示,是TLS的扩展,通过在client hello消息中加入服务名,来实现同一端口多证书(之前因为无法在连接加密前获取服务名称,导致服务器只能使用一张证书)。

目前SNI主要用于HTTPS协议,实现同一服务器多域名。

但其实SNI还有一种特殊用途:SNI代理。SNI代理类似TCP代理,原理就是client把client hello发给proxy,proxy解析出client实际要访问的target,然后proxy去连接target,连接成功后把client hello原封不动地发往target,随后proxy要做的只是把client发来的数据转向target,把target发来的数据转向client。与tcp代理不同,SNI代理是安全的,因为proxy没有私钥,无法解密client发送的数据,也就没有办法计算随后生成的对称密钥。

以前由于无法获取目标服务器名只能静态代理或者反向代理HTTPS网站,有了SNI,动态代理也得以实现。所以一些运营商会通过架设具有高出口带宽的SNI代理服务器,然后劫持用户请求到代理服务器的方式来实现访问加速。

而这些运营商架设的SNI代理大都没有对client和target进行限制,所以就有了以下两种妙用。

一、绕过墙

因为墙目前没有对SNI进行检测,所以通过把被封网站的IP解析到境外的SNI代理服务器就可以实现对封锁网站的访问。但是这种方法只能访问HTTPS网站,而且需要手动配置hosts或dnsmasq,比较麻烦。而chrome插件SwitchyOmega有一项专门用来配置https代理,如果利用SNI代理搭建一个本地代理,再结合这个插件使用会非常方便。以下是利用SNI代理搭建本地代理的python代码,更改其中的serverip为实际境外SNI代理ip即可,本地http代理端口为4433.

 

二、SSH加速

因为这些SNI代理的出口带宽很高,所以通过境内SNI代理访问境外VPS应该比普通宽带用户快。理论上通过把SSHD监听在vps的443端品,然后把原本解析到VPS IP的域名通过hosts改为SNI代理IP,然后就能实现对SSH访问进行加速了,而且通过ssh的动态转发代理访问网站也变快了。但是有以下问题 :

1.SSH目前不支持SNI

这个可以通过socat程序来解决,socat刚刚添加对SNI的支持。如果没有或者用不了socat也可以用python写一个,很简单,就是把两个socket连接起来。

 服务端: socat  openssl-listen:443,fork,reuseaddr   tcp:localhost:22
客户端: socat tcp-l:22,fork,reuseaddr  openssl:SniProxyIp:443,snihost=test.com

然后客户端 ssh user@localhost 就可以了。

2.SSH与HTTPS共享443端口

HTTP是请求响应式的,所以没有办法把SSH包装到HTTP协议中。只能通过监听在443端口,然后根据client hello中的server name来判断转发到HTTPS还是SSH,这个功能可以通过sniproxy程序来实现。具体流程:让nginx/apache的HTTPS监听在端口A,让上面的socat监听在端口B。然后修改sniproxy.conf:

注意test.com要位于最后,否则子域的配置会失效。

c++注意事项

  • 类设计
    1. 构造函数不要全部包含默认参数(加explicit)
    2. setter返回对象本身的非const引用
    3. 当某些行为与类型相关时,就需要考虑使用继承和多态了
    4. 算术操作符+-*/,==定义为全局函数(交换性),另外一个直接用已定义的来定义,在可以转为同一类型的情况下可以只定义一个(比如string,char*)
    5. 前置++ –返回引用,后置++ –返回值
  • 模板
    1. 迭代器可以用来模拟虚拟数列,IO设备
    2. 在有大量算法需要更改时,考虑一下迭代器适配器、函数适配器
    3. 对于模板类,编写一个生成其对象的模板方法
    4. 构造函数也可以是模板函数
    5. 操纵器、应用器、函数对象
    6. 使用模板将应用程序库与IO分离

旮旯

std::function<void(void)>(foo)(); //错误,等价于下行
std::function<void(void)>foo();
(std::function<void(void)>(foo))();//正确
std::function<void(void)>{foo}();//正确