C++中经常被误解的概念是什么?

33

C++中经常被误解的概念有哪些?


类似于:https://dev59.com/b3VD5IYBdhLWcg3wI3-L - Mitch Wheat
更像是https://dev59.com/NXVC5IYBdhLWcg3wcgqd - Tim Matthews
1
也类似于:http://stackoverflow.com/questions/174892/what-is-the-most-spectacular-way-to-shoot-yourself-in-the-foot-with-c - Scott Langham
38个回答

80

C++不是带有类的C语言!

而且也没有叫做C/C++的语言。从那里开始,一切都会走下坡路。


4
哈哈,太对了。我喜欢关于 C/C++ 的那个。 :) - jalf
4
可能没有 C/C++,但我确定有 C/C++/C#。 - MiseryIndex
C/C++确实存在;它是编写程序的概念,可以将其编译为C,但在编译为C++时添加功能或检查。这意味着您可以创建一个代码库,其占用空间不需要C++编译器,但在可用时受益于它。此外,C++的全部存在理由是允许混合使用C和C++代码进行逐步改进-这是人们经常忘记的事实。当然,大多数标记为“c”和“c ++”的问题都不涉及任何一种情况。但是,建议讨论混合C和C++代码是不合法的是错误的。 - HostileFork says dont trust SE
@HostileFork 没有人在建议这样做。我是说C/C++ 语言 不存在。无论如何,C++的原始设计可能是有意的,使得代码库可以逐步从C中提取,但现在不再是这种情况了。许多现代有效的C代码在C++中根本不是有效的,这没关系。而且现在的C++已经不是过去的那个语言了,所以不要把历史和当前的真相混淆。 - Konrad Rudolph

40

C++确实具有自动资源管理。(Most people who claim that C++ does not have memory management try to use new and delete way too much, not realising that if they allowed C++ to manage the resource themselves, the task gets much easier).

例如:(使用虚构的API创建)

// C++
void DoSomething()
{
  File file("/tmp/dosomething", "rb");
  ... do stuff with file...
  // file is automatically free'ed and closed.
}

// C#
public void DoSomething()
{
  File file = new File("/tmp/dosomething", "rb");
  ... do stuff with file...

  // file is NOT automatically closed.
  // What if the caller calls DoSomething() in a tight loop?
  // C# requires you to be aware of the implementation of the File class
  // and forces you to accommodate, thus voiding implementation-hiding
  // principles.
  // Approaches may include:
  // 1) Utilizing the IDisposable pattern.
  // 2) Utilizing try-finally guards, which quickly gets messy.
  // 3) The nagging doubt that you've forgotten something /somewhere/ in your
  //    1 million loc project.
  // 4) The realization that point #3 can not be fixed by fixing the File
  //    class.
}

-1 请解释一下在哪里以及如何实现自动资源管理? - hasen
4
具有讽刺意味的是,对于这个问题最正确的答案可能会被评分最低,_因为_它们更容易被误解。(话虽如此,我应该提供一些关键词来帮助人们搜索参考资料,例如RAII和优先在堆栈上分配对象。) - Arafangion
3
C++拥有可选的自动资源管理。在C++中,auto关键字是(栈分配的)局部变量的默认存储类。使用new关键字标记堆分配时,则需要手动进行资源管理。然而,程序员经常滥用new关键字导致内存泄漏,然后声称C++没有自动资源管理,实际上是他们禁用了自动资源管理。 - Lie Ryan
也就是说:有一种C++编程风格叫做RAII,它可以在适当的范围内使用,并通过正确编写析构函数确保资源不泄漏。这不是自动内存管理,因为程序员需要编写代码来实现析构函数中的管理。与Java相比,Java运行时会回收/销毁不再被引用的内容。这就像苹果和橙子-在C++中,程序员必须管理销毁,而RAII是一种不错的方式,但在Java中,运行时会自动处理。 - user140327
顺便说一下,大约15年前,我写了一个Java应用程序(我的第一个Java应用程序和第三个SQL查询,我很自豪;-)。它在一个非常紧密的循环中遍历了来自SQL查询的大量结果。第一个版本由于内存不足而崩溃...我的循环比垃圾收集器回收它们更快地创建了“行对象”(经过15年后,我已经忘记了行的Java术语是什么)。我想了一会儿,意识到我可以强制进行垃圾收集,再想了一会儿,只是写了一个更好的SQL查询。因此...自动垃圾收集只有程序员使用它才能发挥其作用! - user140327
显示剩余2条评论

35

自由函数并不差只因为它们不在类中 C++ 不仅仅是一门面向对象的语言,而是建立在一整套技术之上。

我经常听到人们说自由函数(那些在命名空间和全局命名空间中的函数)是“C 时代的遗物”,应该避免使用。相反,自由函数允许将函数与特定类解耦,并允许重用功能。如果函数不需要访问实现细节,则建议使用自由函数而不是成员函数 - 因为这将消除更改类的实现时可能引起的级联更改等其他优势。

这也反映在语言本身:在 C++0x 中(即将发布的下一个版本),基于范围的 for 循环将基于自由函数调用。它将通过调用自由函数 beginend 来获取 begin / end 迭代器。


嘿,你不是一两周前还在为成员函数辩护吗?;) 我同意。祝贺你获得金徽章! 顺便说一句,它还不是C++0x吗?据我所知,他们仍然计划在09年完成规范。 - jalf
jalf,好的Herb Sutter在2008年10月说过,很快会有第二个社区草案,大约在2009年10月左右。然后我认为他们会再次等待评论并开会,之后最终的新标准将在2010年出台。这就是我听到的,也是我称之为C++1x的原因 :) - Johannes Schaub - litb
1
我完全同意。开发人员似乎会把所有东西都放在一个类中,无论它是否属于那里。此外,我更喜欢C++0A而不是C++1X。 - deft_code
是的!将不依赖于类内部的任何方法移动到一个自由函数(当然是在相关的命名空间中)的想法,在大多数程序员的“感觉”上似乎有些违反常规,起初我也有同样的感觉,但是当你仔细思考时,它完全合理。 - j_random_hacker
1
这被称为接口原则,是Herb Sutter的想法。至少要给他一些功劳。http://www.gotw.ca/publications/mill08.htm - Piotr Dobrogost
4
@Piotr,让我相信自由函数更优秀的文章并不是Herb Sutters写的,而是Scott Meyers的"How non-member functions improve encapsulation"(非成员函数如何改善封装性):http://www.ddj.com/cpp/184401197。没有查找谁对此最有影响力,我认为这不是可以归功于某个人的事情。相反,社区中的机制和想法已经逐渐发展壮大,并且所有成员都会在某种程度上促成其中的成果。 - Johannes Schaub - litb

27

赋值和初始化的区别:

string s = "foo";    // initialisation
s = "bar";           // assignment

初始化始终使用构造函数,赋值始终使用operator=运算符


8
我总是使用括号进行初始化(例如 string s("foo");),以帮助保持区别明显。在构造函数中,初始化和赋值之间的差异也容易被误解。 - Chris Smith
这怎么是一个“被误解的概念”? - Nippysaurus
另一个例子可能是Object o(1);与Object o = 1;,其中Object具有复制构造函数和一个单参数构造函数,该构造函数接受一个int。 - James Schek

22

按降序排列:

  1. 确保释放已分配内存的指针
  2. 当析构函数应该是虚拟的时候
  3. 虚函数的工作原理

有趣的是,很少有人知道虚函数的全部细节,但似乎仍能顺利完成工作。


谢谢。确实是一个痛点。 - Sesh
2
使用智能指针来管理分配的内存,这将使你的生活变得更加轻松。 - David Thornley
@David - 你说得对。然而,问题是关于C++中被误解的概念,至少在我看来,内存管理是最重要的。 - Sesh
@Sesh:使用智能指针,或确保删除已分配内存的原始指针,这个怎么样? - David Thornley
@Sesh + @David:如果你们真的在使用C++,就不应该手动delete指针(除非你自己正在实现智能指针)。因此,我认为这本身就是一个误解。 - Billy ONeal
显示剩余2条评论

21
我见过的最危险的概念是将C++视为具有一些附加功能的C语言。实际上,使用现代C++系统应该将其视为不同的语言,我所看到的大部分C++贬低都基于“带有添加功能的C”模型。
要提一些问题:
虽然你可能需要了解delete和delete[]之间的区别,但通常你都不需要编写它们。使用智能指针和std::vector<>。
事实上,你应该只很少使用*。对于字符串,请使用std::string。(是的,它设计得很糟糕。仍然要使用它。)
RAII意味着您通常不必编写清理代码。清理代码风格不好,破坏了概念上的局部性。作为奖励,使用RAII(包括智能指针)可以免费获得很多基本的异常安全性。总体而言,在某些方面比垃圾收集更好。
一般来说,类数据成员不应直接可见,无论是通过public还是getter和setter。有例外情况(例如点类中的x和y),但它们是例外情况,应作为例外情况考虑。
最大的问题是:根本没有C/C++这样的语言,可以编写能够在任何语言下正确编译的程序,但这种程序既不是好的C++程序,也不是好的C程序。自从Stroustrup开始研究“带类的C”以来,语言一直在分化,现在比以往任何时候都要不同。将“C/C++”用作语言名称是初步证据表明用户不知道自己在谈论什么。正确使用的C++与C一样,就像Java或C#一样。

一个以C语言编写的程序,如果能够编译成C++,通常来说并不是一个好的C程序。在我的经验中,几乎所有的C程序都可以编译成C++。 - Imbue
取决于它的功能。"int * a = (int *)malloc(...)" 不是真正好的 C 语言,而 "int * a = malloc(...)" 在 C++ 中无法编译。在任何妥协的地方,C++ 方面通常不如 C 方面好。 - David Thornley
3
std::string 并不是设计糟糕 -- 它只是没有将大部分功能作为其签名的一部分。因此,应该使用 STL 风格的算法来处理它。许多 C++ 程序员认为 std::string 是 Java 或 C# 字符串的劣质版本,而不是完整的 STL 容器。 - Billy ONeal
1
据我所知,C++是通过模板编程实现的开放式逻辑系统。而C则是一个封闭的系统。在C中,A就是A,但在C++中,A可能是AA<T>。我不知道这对你来说是否有意义,但对我来说是有意义的。 - user140327
@BillyONeal 是的,并且在此基础上添加必要的 boost 字符串算法,它可以与 Python 或 C# 相媲美。而且在 C++11 中,现在还有一些面向 UTF 编码的 facets 可以使用,例如 codecvt 头文件。 - v.oddou
@DavidThornley:你为什么认为std::string设计得不好? - Destructor

19

过度使用与多态无关的继承。大多数情况下,除非您真正使用运行时多态性,否则组合或静态多态性(即模板)更好。


2
不错!尽管可能不仅限于C++用户。 - xtofl
当然不仅局限于C ++,尽管使用模板比Java等语言提供了更多的选择。 - KeithB
再说,Java现在已经有了泛型,但是仍然需要对基本类型进行装箱/拆箱处理... - Arafangion
4
Java 泛型并不能替代 C++ 模板系统。参见此问题:http://stackoverflow.com/questions/498317/c-templates-and-java-generics-difference - KeithB
这个答案与多态性有关。 - Arafangion

13

静态关键字根据使用位置的不同,可以有三种不同的含义。

  1. 它可以是一个静态成员函数或静态成员变量。
  2. 它可以是在命名空间范围内声明的静态变量或函数。
  3. 它可以是在函数内部声明的静态变量。

我只记得其中两个:声明静态方法和声明静态变量,第三个是什么? - isekaijin
我是说一个静态成员,不一定是一个方法。 - isekaijin
请注意,使用方式#2已被弃用。对于此用例,未命名的命名空间优先于静态关键字。 - Brian Neal
我错过了什么?如果未命名的命名空间可以替换#2,那么如何区分过去具有不同命名空间以区分它们的两个完全相同命名的函数/变量? - Dunk
邓克:你是什么意思?如果它们在未命名的命名空间中,它们在当前编译单元之外不可见,因此它们不会与其他编译单元中具有相同名称的符号发生冲突。如果它们在同一编译单元中,则违反了ODR,并且是非法的。 - jalf
@Chris:http://en.wikipedia.org/wiki/One_definition_rule - Roger Pate

13

数组不是指针

它们是不同的。因此,&array 不是指向指针的指针,而是指向数组的指针。在我看来,这是 C 和 C++ 中最被误解的概念。你必须访问所有那些告诉你要将二维数组作为 type** 传递的 SO 回答!


12

以下是一些内容:

  1. 使用模板来实现类似ATL的无需虚表的多态。
  2. 内存中的逻辑const和实际const之间的区别。何时使用mutable关键字。

致谢:感谢spoulson纠正我的错误。

编辑:

以下是更多内容:

  1. 虚继承(不是虚方法):事实上,我完全不理解它!(我的意思是我不知道它是如何实现的)
  2. 联合体的成员是具有非平凡构造函数的对象的类。

错别字:应为“mutable”,而非“mutabile”。 - Klaim
我不理解第一点 - 或者说,我理解了,但完全不同意。 - Konrad Rudolph
我同意第一点,但我不明白它为什么是一个被误解的概念?这更像是C++的一个特性,而不是一个被误解的东西。你能否请说明一下哪里被误解了? - Johannes Schaub - litb
@Konrad:如果你不同意第一点,那么至少你或者作者误解了它。这是一个50%的误解。而且通常这已经足够成为这个问题的一个好答案了。[也许我的逻辑有点反常...我想我需要睡觉了]。 - Scott Langham
Scott,我也这么想,哈哈。如果他说这是一个有效的观点,而Konrad说它不是,那么他们中的一个人没有理解它 :p - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接