百科问答小站 logo
百科问答小站 font logo



C++ 有多难? 第1页

  

user avatar    网友的相关建议: 
      

C++难就难在:在C++中你找不到任何一件简单的事。

上面有人把C++和物理作类比。我很同意。理论物理是一场无尽的旅程,总有最前沿的东西。我对神经科学很感兴趣,也有幸与一个神经科学相关专业的学生交流过,她还给我发过资料,我很感激。然而我在知乎上看到过一个相关的讨论。一个人说“我小时候就想知道大脑是如何工作的,于是我学了神经科学,如今我已经是神经科学博士,依然不知道大脑是如何工作的”。所以我的求知欲只能暂且到此为止。C++亦是如此。

扯远了,我们来说C++有多难吧。

我们只谈构造函数。假如我们有一个类Teacher。

       class Teacher { private:     std::string name;     std::string position; };     

我们考虑给Teacher类加上构造函数。

       class Teacher { private:     std::string name;     std::string position;  public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {} };     

虽然语义正确,但是如果我们的实参只为了传递给Teacher,传递之后而没有其他作用的话,那么这个实现是效率低下的。字符串的拷贝花销可观(关于std::string的COW,SSO,view的讨论是另一个故事了)。我们在C++11里面有右值引用move语义,所以呢,我们可以改成这样。

       class Teacher { private:     std::string name;     std::string position;  public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {}      Teacher(std::string&& n, std::string&& p)         : name(std::move(n)), position(std::move(p)) {}; };     

你可能觉得这样也已经不错了。不过我们还有可能第一个参数右值,第二个参数左值。或者第一个参数左值,第二个参数右值。所以实际上我们需要四个函数的重载。

       class Teacher { private:     std::string name;     std::string position;  public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {}      Teacher(std::string&& n, std::string&& p)         : name(std::move(n)), position(std::move(p)) {};      Teacher(const std::string&& n, const std::string& p)         : name(std::move(n)), position(p) {}      Teacher(const std::string& n, const std::string&& p)         : name(n), position(std::move(p)) {} };     

代码有点多。我们有没有什么方法写一个通用的函数来实现这四个函数呢?有。我们在C++11中有完美转发

       class Teacher { private:     std::string name;     std::string position;  public:     template <typename S1, typename S2>     Teacher(S1&& n, S2&& p)         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };     

完成了。美滋滋。然而事情没有这么简单。如果我们的position有默认值,然后我们写如下代码的话。

       class Teacher { private:     std::string name;     std::string position;  public:     template <typename S1, typename S2 = std::string>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };   int main() {     Teacher t1 = { "david", "assistant" };     Teacher t2{ t1 }; }     

我们出现了编译期错误。因为Teacher t2{ t1 };重载决议的最佳匹配是我们的模板,而不是默认的拷贝构造函数,因为拷贝构造函数要求t1是const的。所以,我们可能需要SFINAEtype traits来修改我们的代码。注意,默认函数参数不能类型推导,所以我们才需要的S2的默认模板参数。

       class Teacher { private:     std::string name;     std::string position;  public:     template <typename S1, typename S2 = std::string,     typename = std::enable_if_t<!std::is_same_v<S1, Teacher>>>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };      

仍然不对哦,因为我们的完美转发有引用折叠机制,我们应该判断的S1是Teacher&而不是Teacher。其次,如果有类继承我们的Teacher的话,拷贝的时候依然会出现这个问题,所以我们需要的不是is_same而是is_convertible。然而,如果我们直接写std::is_convertible_v<S1, Teacher>的话,我们实际上判定是不是可以转换的时候,还是会去看我们的构造函数。也就是说我们自己依赖了自己,无穷递归。所以我们需要的是std::is_convertible_v<S1, std::string>。所以,我们修改我们的代码。

       class Teacher { private:     std::string name;     std::string position;  public:     template <typename S1, typename S2 = std::string,     typename = std::enable_if_t<std::is_convertible_v<S1, std::string>>>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };     

其次,因为我们的默认参数是字面量,字面量是const char[]类型的。我们调用构造函数的时候,也会用字面量。字面量不是std::string类型会造成很多问题。然而在C++14中,我们可以用User-defined literals来把字面量声明成std::string类型的。不过记得命名空间,这个名字空间不在std中。我们这里不再讨论了。

我们接下来讨论用构造函数初始化的问题。初始化有很多种写法,以下我列出有限的几种。

       Teacher t1("david"s); Teacher t2 = Teacher("david"s);  Teacher t3{ "lily"s }; Teacher t4 = { "lily"s }; Teacher t5 = Teacher{ "lily"s };  auto t6 = Teacher("david"s); auto t7 = Teacher{ "lily"s };     

我们用了auto。然而auto是decay的。而decltype(auto)不。所以,以下代码如果用auto的话可能不是你需要的。

       const Teacher& t8 = t1; auto t10 = t8;     

我们需要写const auto&

此外,我们可以看出,用小括号和大括号好像没什么区别。不过,在一些情况下会有很大的差别。我们列出一些。

       std::vector<int> vec1(30, 5); std::vector<int> vec2{ 30, 5 };     

甚至因为C++17的构造函数自动推导,我们可以写出更加疯狂的代码。

       std::vector vec3{ vec1.begin(), vec2.end() };     

这个代码是用初始化列表初始化的,也就是说我们得到的vec3中有两个iterator。

好了,我们回过头来说auto。我们可以看到好像我们所有的初始化都可以用auto。是这样吗?如果我们写atomic的代码呢?

       auto x = std::atomic<int>{ 10 };     

是可以的。因为在C++17中我们有Copy Elision。所以这里没有拷贝函数的调用,和直接定义并初始化是一致的。但是atomic初始化是有问题的。

       std::atomic<int> x{};     

这样是不能零初始化的。当然了,这显然是API的不一致,或者说错误。所以我们有LWG issue 2334。预计在C++20修复这个问题。嘻嘻。

以上内容基于《C++ templates》的作者Nicolai Josuttis的几场talk。

最后不知道说什么,祝大家新年快乐。


user avatar   heng-xu-zhi-jing 网友的相关建议: 
      

C++之难不在于其语法的复杂性,也不在于二进制层面上语义的杂乱无章,更不在于玄妙得不食人间烟火的模板推导(模板元编程),这些都只是表象。本质上讲,C++跟任何语言比,它很独特很怪异(废话,任何一种语言那个不特异)。

很多时候,C++给人的感觉就是,好像任何一种语言的特性(这话有点夸张),都可以在C++王国中,通过令人发指的奇技淫巧,罄竹难书的花样作死,最后终于可以在一定程度上模拟出来,但是模拟后的结果,又总是存在这样那样的不足,要么因为内存管理,要么因为反射的原因,总之,就是好像可以做一切事情,但最后终于做得不好。这个时候,猿猴要么就直接扑上原生带有这种特性的语言,要么干脆就完全舍弃,放弃治疗,啥技巧也不用,返璞归真,就老老实实一行代码一行代码、不厌其烦、不畏枯燥地一再写地重复类似的功能。而C++自身的优秀特性(析构函数、内存管理、模板、多继承等等),没有任何一种语言整的出来,当然,也可以说,这些玩意都是为了解决C++自身制造出来麻烦,other语言s完全不care这些杂碎。难道,这些好东西就没有一丁点价值了。

更令人难堪的是,迄今为止,C++业界就没有出现过方方面面都让人满意的基础库,也即是性能(对c++猿来说,性能最重要)、可扩展性、易用性、安全性都经得起推敲。所有的通用库、流行库都存在这样那样的诟病,stl如是,boost如是,qt如是,……。所以,就开始有人(现在是普遍都持这种观点啦,十几年前还只是开始)怀疑了,为什么其他语言出来不久,就马上配套相应的官方基础权威平台框架库,就算是C语言,也有标准头文件库,里面也确实没有可争议之处。就C++诸多借口,迟迟交不出答卷。这一定是语言的问题,毫无疑问。就算不是语言的问题,你看看,几十年下来,多少大牛,就搞不出来的东西,由此可见,C++有多麻烦,有多复杂,平常人hold得住吗?

可是,基于C语言,C++多出来任何特性,都确确实实很有价值,用得好,的而且确,可以节省很多很多重复代码。就算最让人诟病的隐式类型转换,虽然一不小心,就给猿猴惊喜,带来理解上麻烦。但是,在可控的情况下,真的可以少写烦心的代码,某些场合,也是奇技淫巧的用武之地。好吧,既然low C都能写出来基础通用库,反而高大上的C++就举步维艰了。问题是,精致的C代码,到了C++舞台上,马上就备受一连串很有道理的指责,类型不安全啦,缺乏弹性啦,不够易用性,甚至连性能(明明在C那里就是极限了),也可以榨出汁来。既然你大C++这么厉害,你行你上啊,少在这里瞎逼逼。几十年了,你换了多少次新马甲,依然虚有其表,金玉其外。可以想象大C++的老脸有多红啊。

所以说,这个世界的猿猴语言分为两大类,C++与其他语言。其他语言稍微努力就能做出来很有群众基础的通用库(不接受也不行,压根就不让你做文章,就不给你后门更好地实现),进而跑步进入共产主义,人生苦短。而C++看着其他语言的通用库,就会瞎逼逼,指指点点,这里不好,那里不对,但是自己无论如何,就是只能做出来小团体运用,自我陶醉的通用库,这些所谓的通用库,最后多半要被历史潮流所淘汰。

当你废了九牛二虎之力搞清楚了C++的对象模型(继承、多继承、虚继承、异常、……),各种数据类型在内存中表示,还搞清楚不同编译器的不同处理方式;兴致勃勃搞起模板元编程从入门到放弃;预处理的伪图灵完备也玩出翔来;将mfc操得体无完肤;对stl、boost也深挖祖坟以至深感失望(stl还好,boost真心烂);C++编译器也被操得死去活来;……,这都有多少年过去了,依然感觉写不好C++代码,依然充满疑惑,代码写来写去,总是感觉写的不对,似乎还可以提升,还可以提升,可以在牺牲性能、类型安全、弹性、易用性的前提下继续加强性能、类型安全、弹性、易用性,你不知道C++的上限在哪里。直到有一天,那一刻,终于到达彼岸。

对于普通用户来说,C++最大的问题,就是缺乏一个高水准高质量的基础通用库,这个库,首先要坚持住零惩罚抽象的底线,不管怎么样,都不得妥协,历史的经验证明,在这一点上退缩的基础库,最后都将导致整个设计框架上的冗余,倒不仅仅只是因为性能的原因。其次,在性能、弹性、使用接口、类型安全等综合方面都要取到很好的平衡点,也就是说用这个库说人话的时候,其性能一定要不差,好用,有弹性,类型上用得不对,编译器就不满意。但是这个基础库又要像大C++语言本身一样,充满后门,只要愿意,随时都可以为了提升某一要素,可以牺牲任何其他要素(比如说为了性能,将使用接口、弹性上搞得很难看)。具体展开来说,这个基础库包括,完备的内存管理(支持一定程度的gc效果,其实就是多次分配,一次统一释放);完备的运行时类型信息;极大地挖掘template的潜力;编译期与运行期的无缝对接,二进制语义上的清晰。至于再具体来说,比如字符串设计,格式化,序列化,IO,侵入式的容器,迭代器,协程等,就是细微末节了。

显然,以上述的要求来评判stl,显然stl很不行的,虽然勉勉强强坚持住零惩罚的底线,但是总体来说,其性能、弹性、使用接口、类型安全的总体分数上是相当低的。1、竟然脑洞大开,将内存管理当做是容器的类型参数来操作,这样玩的严重可怕后果,就不说stl的东西不能很好地用于动态库,想要做有gc效果的内存管理也不好办了;2、对模板的使用只停留在很低的层次上,本来可以提供更多更有力的抽象机制,比如非侵入式接口,比如消息,比如异构容器;3、回避虚函数,回避反射,导致stl的运行时类型信息很薄弱,进而导致垃圾的io stream实现,对面向对象的支持极差,也导致痛苦漫长的编译过程;4、对于编译期的丰富类型信息,只会用类型拭擦一招带到运行期,导致到运行期时丢失了很多重要的信息;5、对于容器的二进制布局,回避,不花心思,所以二进制的复用效果很差;……,算了,对stl的不满,简直是说三天三夜也说不完。所以,以stl为基础来写代码,能不一再反复造轮子,写代码能愉快?

所以,C++的难,说到底,只是通用基础库的实现之难。C++的难,是C++专家的难题,并非普通用户的麻烦。那么,实现通用基础库的难度有多大,从C++面试至今三十多年,还没出现过,你说会有多难呢?那么,为什么会这么难呢?

一直以来,C++专家对C++的认识,一直停留在很低的层面上。面对着C++复杂庞大的类型系统,完全开放式的内存管理,直接操作机器的种种方便,威力无比的template,很丑陋又好像很重要很有作用的预处理,厚颜无耻的多继承以及不定时炸弹的异常,这些东西造就了C++无限可能的同时,也造成了C++在打造基础通用的极大困难。表面上存在无穷无尽的选择,但是正确的路子,真心不多。一不小心,就面临无穷无尽的细节上的考究,最后造出来的轮子,反而引入更多的麻烦。而更糟糕的是,很多人更高估了自己对C++的认识,贸贸然就随随便便造轮子,还大规模的在代码上到处泛滥。(待续)


user avatar   deletedeletedelete 网友的相关建议: 
      

以前招聘C++的人,看到一个简历里写".......踩过3年C++的坑......",我直接通知HR让他来面试了,后来工作没多久就发现这哥们C++水平比我高,我感觉比当时我们另一个8年C++经验的老程序员水平都高,半年左右后发现这哥们其实能不用C++就不用.

水平高的程序员通常都很聪明,掌握了很棒的学习方法,学东西贼快,而且通常能快速抓到问题的重点,通常都会很多种编程语言,理解各种语言的优缺点,会权衡取舍,会在不同的场景选择合适的语言办事,不会固执的抱着一个语言当传家宝.


user avatar   zhao-ce-33 网友的相关建议: 
      

当感情成为生活的一种负担而且无力改变的时候。

与女生不同,大多数男生是不会把感情当作生活的全部的,过日子是头等大事,感情只是其中一部分。所以对于男生来说,一段好的感情是能让生活更轻松的。

一个男生可能会因为你漂亮而喜欢你,但这种喜欢无非是荷尔蒙冲动无法长时间维系,真正能让男生愿意和你一辈子走下去的,是你能够理解他,能够支持陪伴,能够默契相处。

但就这一点上,很多女生都是在逆行。她们所期望的感情都是建立在对于男生的索取之上,她们会因为男朋友没有足够专注自己而去作,会因为男朋友没满足自己的需要而去闹,会频繁吵架提分手来试探这个男人是否足够爱自己,却忽略了这个过程中,自己一步步在抛掉自己值得被爱的筹码。

当男生觉得和你在一起,不但体会不到丝毫乐趣,反而成为一种负担的时候,其实就已经在积累失望了。但这个时候男生一般不会直接放弃,他们会尝试逃避,会尝试沟通,会寄希望于你能逐渐去改变。

所以有经验的女生应该知道,男人的态度变化是有个周期的,不会因为你和他吵几次而一下子和你分手,但是会逐渐冷漠下来,这个过程中他就是在开始怀疑是否真的合适,会希望看到你有所变化。然而往往女生在这个阶段,又会因为男生的逃避而更加变本加厉去作去闹,想要控制对方,最终形成恶性循环。

等到失望积累够了,也就变成绝望。他会判断你已经不会变了,无药可救了,再相处下去只会让那自己越来越累,最终决定分开,等你意识到问题所在时已经来不及了,这种理性思考下的分手,往往挽回难度也是比较大的。

很多女生整天担心男生变心不爱自己,但同时却又是亲手在不断把他往外推,想想还挺可笑的。




  

相关话题

  假如能带自己的电脑回到 2000 年,你会安装什么软件?带什么资料? 
  C++过于复杂为什么不被淘汰? 
  今年刚上岸,跟一个老师学习了一段时间后,可以换老师吗? 
  为什么要有指针? 
  品胜是如何成为国内数码配件的龙头品牌的? 
  如何评价「线程的本质就是一个正在运行的函数」? 
  子网掩码与ip地址有实际关系吗? 
  如何评价2021年amd服务器份额跌至1.4%? 
  如何理解互斥锁、条件锁、读写锁以及自旋锁? 
  男朋友的大脑是单线程的怎么办? 

前一个讨论
在职程序员们,如何看待高校学生的技术不断更新迭代?
下一个讨论
网红真的是靠自己红起来的么?





© 2024-05-17 - tinynew.org. All Rights Reserved.
© 2024-05-17 - tinynew.org. 保留所有权利