Java开发人员是否有意识地放弃了RAII?


82

作为一个长期的C#程序员,我最近来了解有关资源获取即初始化(RAII)的优点的更多信息。特别是,我发现了C#习惯用法:

using (var dbConn = new DbConnection(connStr)) {
    // do stuff with dbConn
}

具有C ++等效项:

{
    DbConnection dbConn(connStr);
    // do stuff with dbConn
}

这意味着记住以包围资源的使用像DbConnection在一个using块是在C ++不必要!这似乎是C ++的一大优势。当您考虑一个类的实例成员类型为时,这甚至更令人信服DbConnection

class Foo {
    DbConnection dbConn;

    // ...
}

在C#中,我需要IDisposable像这样实现Foo :

class Foo : IDisposable {
    DbConnection dbConn;

    public void Dispose()
    {       
        dbConn.Dispose();
    }
}

更糟糕的是,每个用户Foo都需要记住将其括Foo在一个using块中,例如:

   using (var foo = new Foo()) {
       // do stuff with "foo"
   }

现在来看一下C#及其Java根源,我想知道... Java开发人员是否完全欣赏他们放弃堆栈而转而使用堆而放弃RAII时所放弃的一切?

(同样,Stroustrup是否完全理解RAII的重要性?)


5
我不确定您在说什么不将资源封装在C ++中。DBConnection对象可能会处理关闭其析构函数中的所有资源。
maple_shaft

16
@maple_shaft,正是我的意思!这就是我要解决的C ++的优点。在C#中,您不需要将资源包含在“使用中...”中,而在C ++中则不需要。
JoelFan 2011年

12
我的理解是,只有在C ++编译器足以实际使用高级模板的情况下,才可以理解RAII作为一种策略,这在Java之后是很不错的。创建Java时实际上可用的C ++是一种非常原始的“带有类的C”样式,如果有幸的话,可能带有基本模板。
肖恩·麦克米伦

6
“我的理解是,只有在C ++编译器足够好以至于可以实际使用高级模板之后,才可以理解RAII作为一种策略,而Java早就可以了。” -那不是真的。从第一天开始,构造函数和析构函数就一直是C ++的核心功能,远远早于模板的广泛使用和Java之前。
Jim in Texas

8
@JimInTexas:我认为肖恩在某处有一个基本的真理种子(尽管不是模板,但关键是例外)。构造函数/析构函数从一开始就是存在的,但是它的重要性和RAII的概念最初(我要寻找的词)并没有意识到。在我们意识到整个RAII的重要性之前,编译器花了好几年时间才能变得良好。
马丁·约克

Answers:


38

现在来看一下C#及其Java根源,我想知道... Java开发人员是否完全欣赏他们放弃堆栈而转而使用堆而放弃RAII时所放弃的一切?

(同样,Stroustrup是否完全理解RAII的重要性?)

我可以肯定的是,戈斯林在设计Java时并没有获得RAII的重要性。在采访中,他经常谈到遗漏泛型和运算符重载的原因,但从未提及确定性析构函数和RAII。

有趣的是,甚至在Stroustrup设计他时,他都不知道确定性析构函数的重要性。我找不到报价,但是如果您真的喜欢它,可以在他的采访中找到它:http : //www.stroustrup.com/interviews.html


11
@maple_shaft:简而言之,这是不可能的。除非您发明了确定性垃圾回收的方法(这似乎通常是不可能的,并且在任何情况下都使过去几十年的所有GC优化无效),否则您必须引入堆栈分配的对象,但这会打开多个容器。蠕虫:这些对象需要语义,带有子类型的“切片问题”(以及因此没有NO多态性),悬空的指针,除非您对它施加了很大的限制或进行了大范围不兼容的类型系统更改。那就是我的头上。

13
@DeadMG:所以您建议我们回到手动存储管理。一般来说,这是一种有效的编程方法,当然,它还允许确定性销毁。但这并不能回答这个问题,该问题与仅使用GC的设置有关,即使我们都像白痴一样,该设置也要提供内存安全性和明确定义的行为。这需要GC进行所有操作,并且没有办法手动启动对象销毁(并且存在的所有 Java代码至少都依赖于前者),因此您要么使GC具有确定性,要么就不走运。

26
@delan。我不会称之为C ++智能指针manual内存管理。它们更像是确定性的细颗粒可控垃圾收集器。如果使用正确,智能指针就是蜜蜂的膝盖。
马丁·约克

10
@LokiAstari:好吧,我想说它们的自动化程度要低于完整的GC(您必须考虑一下您真正想要的智能程度),并将其实现为库需要在其上建立原始指针(因此需要手动进行内存管理) 。另外,除了自动处理循环引用外,我不知道任何智能指针,这是我书中垃圾收集的严格要求。智能指针无疑是非常酷和有用的,但是您必须面对它们不能为完全专有的GC语言提供某些保证(无论您是否认为它们有用)。

11
@delan:我必须在那里不同意。我认为它们具有确定性,因此比GC更自动化。好。为了提高效率,您需要确保使用正确的(我会给你的)。std :: weak_ptr可以很好地处理周期。周期总是被淘汰,但实际上它几乎从来不是问题,因为基础对象通常是基于堆栈的,当它走时它将整理其余部分。在极少数情况下,这可能是std :: weak_ptr的问题。
马丁·约克

60

是的,C#(当然,我确定是Java)的设计人员特别决定反对确定性终结。我在1999-2002年间多次问过安德斯·海斯伯格(Anders Hejlsberg)。

首先,基于对象是基于堆栈还是基于堆的不同语义的想法肯定与两种语言的统一设计目标背道而驰,这正是使程序员免于此类问题的困扰。

其次,即使您承认有优势,簿记也存在很大的实施复杂性和低效率。您不能真正使用托管语言将类似堆栈的对象放在堆栈中。您只剩下说“类似栈的语义”,并致力于大量的工作(值类型已经足够困难,考虑一个对象,它是一个复杂类的实例,其中的引用会传入并返回到托管内存中)。

因此,您不希望在“(几乎)所有事物都是一个对象”的编程系统中对每个对象进行确定性终结。所以,你必须引入某种编程控制的语法来常跟踪的对象从一个具有确定性终结分开。

在C#中,您拥有using关键字,该关键字在后来成为C#1.0的设计中排在很晚。整个IDisposable过程非常糟糕,并且一个人想知道using,使用C ++析构函数语法来~标记那些IDisposable可以自动应用样板模式的类是否会更优雅?


2
C ++ / CLI(.NET)所做的工作如何?托管堆上的对象还具有基于堆栈的“句柄”,它提供RIAA?
JoelFan 2011年

3
C ++ / CLI具有非常不同的一组设计决策和约束。这些决定中的一些决定意味着您可以要求程序员更多地考虑内存分配和性能方面的问题:整个“给自己足够的绳索来吊死自己”的权衡取舍。而且我认为C ++ / CLI编译器比C#复杂得多(尤其是在早期)。
拉里·奥布莱恩

5
+1这是到目前为止唯一正确的答案-这是因为Java故意没有基于(非原始)基于堆栈的对象。
BlueRaja-Danny Pflughoeft

8
@彼得泰勒-对。但是我觉得C#的不确定性析构函数的价值微乎其微,因为您不能依靠它来管理任何类型的受限资源。因此,我认为,使用~语法作为语法糖可能会更好IDisposable.Dispose()
Larry OBrien

3
@拉里:我同意。C ++ / CLI 使用~作为语法糖IDisposable.Dispose(),它是比C#语法方便多了。
2011年

41

请记住,Java是1991-1995年开发的,当时C ++是一种非常不同的语言。异常(使RAII成为必需)和模板(使实现智能指针更容易)是“新颖的”功能。大多数C ++程序员都来自C,并且习惯于进行手动内存管理。

因此,我对Java的开发人员是否故意放弃RAII表示怀疑。但是,对于Java而言,这是一个故意的决定,它倾向于使用引用语义而不是值语义。 确定性破坏很难以参考语义语言实现。

那么,为什么要使用引用语义而不是值语义呢?

因为它使语言很多简单。

  • FooFoo*之间或在foo.bar和之间不需要语法上的区别foo->bar
  • 当所有分配都需要复制指针时,就不需要重载分配。
  • 不需要复制构造函数。(有时需要像这样的显式复制函数clone()。许多对象只是不需要复制。例如,不可变对象则不需要。)
  • 无需声明private副本构造函数operator=并使类不可复制。如果您不希望复制类的对象,则只需不编写函数即可复制它。
  • 不需要swap功能。(除非您正在编写排序例程。)
  • 不需要C ++ 0x样式的右值引用。
  • 不需要(N)RVO。
  • 没有切片问题。
  • 由于引用具有固定的大小,因此编译器更容易确定对象布局。

引用语义的主要缺点是,当每个对象潜在地具有对其的多个引用时,很难知道何时删除它。您几乎必须具有自动内存管理功能。

Java选择使用非确定性垃圾收集器。

GC无法确定吗?

是的,它可以。例如,Python的C实现使用引用计数。后来又添加了跟踪GC来处理引用计数失败的循环垃圾。

但是重新整理效率低下。许多CPU周期花费在更新计数上。在需要同步这些更新的多线程环境(如Java设计的环境)中,情况更糟。在需要切换到另一个垃圾收集器之前,最好使用空垃圾收集器

您可以说Java选择了优化常见情况(内存),但以文件和套接字等不可替代的资源为代价。如今,鉴于在C ++中采用了RAII,这似乎是错误的选择。但是请记住,Java的大多数目标受众是C(或“带类的C”)程序员,这些程序员曾经明确地关闭了这些东西。

但是C ++ / CLI“堆栈对象”呢?

它们只是(原始链接)的语法糖Dispose,就像C#一样。但是,它不能解决确定性销毁的一般问题,因为您可以创建一个匿名对象,而C ++ / CLI不会自动处理它。usinggcnew FileStream("filename.ext")


3
此外,还有不错的链接(尤其是第一个链接与本次讨论高度相关)
BlueRaja-Danny Pflughoeft

using语句很好地处理了许多与清理相关的问题,但仍然存在许多其他问题。我建议一种语言和框架的正确方法是声明性地区分“拥有”引用的存储位置和没有“引用”的存储位置IDisposable。覆盖或放弃拥有引用IDisposable对象的存储位置应在没有相反指令的情况下处置目标。
超级猫

1
“不需要复制构造函数”听起来不错,但在实践中却失败了。java.util.Date和Calendar也许是最臭名昭著的例子。没有比这更可爱的了new Date(oldDate.getTime())
凯文·克莱恩

2
iow RAII并没有被“抛弃”,它根本就不存在被抛弃的问题:)对于复制构造函数,我从不喜欢它们,太容易出错,当有人在某个地方深处时,它们总是令人头疼(否则)忘记制作深层副本,导致资源在不应该复制的副本之间共享。
jwenting

20

Java7引入了类似于C#的内容usingtry-with-resources语句

try声明一个或多个资源的语句。一个资源是程序与它完成后,必须关闭的对象。该try-with资源语句确保每个资源在发言结束时关闭。任何实现的对象(java.lang.AutoCloseable包括所有实现的对象)java.io.Closeable都可以用作资源...

因此,我想他们要么没有意识地选择不实施RAII,要么就改变了主意。


有趣,但看起来这仅适用于实现的对象java.lang.AutoCloseable。可能没什么大不了的,但是我不喜欢这种感觉有些受限制。也许我还有其他一些应该自动释放的对象,但是从语义上讲,使其实现很奇怪AutoCloseable……
FrustratedWithFormsDesigner

9
@帕特里克:嗯,是吗? using与RAII不同-在一种情况下,调用方担心配置资源,在另一种情况下,被调用方处理资源。
BlueRaja-Danny Pflughoeft

1
+1我对试用资源一无所知;在倾销更多样板时应该很有用。
jprete 2011年

3
using/ try-with-resources与RAII不同时为-1 。
肖恩·麦克米伦

4
@Sean:同意。using而且距离RAII还很远。
DeadMG

18

Java特意没有基于堆栈的对象(即值对象)。这些是使对象在方法结束时自动销毁的必要条件。

因此和Java都是垃圾回收的事实,确定性的确定或多或少是不可能的(例如,如果我的“本地”对象在其他地方被引用怎么办?那么当方法结束时,我们不希望它被破坏)

但是,这对我们大多数人都很好,因为几乎不需要确定性的终结处理,除非与本机(C ++)资源进行交互!


为什么Java没有基于堆栈的对象?

(除了基本类型。)

因为基于堆栈的对象与基于堆的引用具有不同的语义。想象一下下面的C ++代码;它有什么作用?

return myObject;
  • 如果myObject是基于本地堆栈的对象,则将调用copy-constructor(如果将结果分配给某些对象)。
  • 如果myObject是基于本地堆栈的对象,并且我们正在返回引用,则结果是不确定的。
  • 如果myObject为成员/全局对象,则调用复制构造函数(如果将结果分配给某些对象)。
  • 如果myObject是成员/全局对象,并且我们正在返回引用,则返回引用。
  • 如果myObject是指向基于本地堆栈的对象的指针,则结果是不确定的。
  • 如果myObject是指向成员/全局对象的指针,则返回该指针。
  • 如果myObject是指向基于堆的对象的指针,则返回该指针。

现在,相同的代码在Java中做什么?

return myObject;
  • myObject返回对的引用。变量是局部变量,成员变量还是全局变量都没有关系。无需担心基于堆栈的对象或指针情况。

上面显示了为什么基于堆栈的对象是C ++中编程错误的常见原因。因此,Java设计人员将它们淘汰了。没有它们,在Java中使用RAII毫无意义。


6
我不知道您的意思是“ RAII中没有意义” ...我认为您的意思是“没有能力用Java提供RAII” ... RAII独立于任何语言...它不变得“毫无意义”,因为1种特定语言没有提供它
JoelFan 2011年

4
那不是正当的理由。使用基于堆栈的RAII,对象不必真正驻留在堆栈上。如果存在“唯一引用”之类的问题,则析构函数一旦超出范围就可以被解雇。例如,查看它如何与D编程语言一起使用:d-programming-language.org/exception-safe.html
Nemanja Trifunovic

3
@Nemanja:对象不必具有基于堆栈的语义可以存在于堆栈中,我从未说过。但这不是问题。正如我提到的,问题在于它们本身是基于堆栈的语义。
BlueRaja-Danny Pflughoeft

4
@Aaronaught:魔鬼在“几乎总是”和“大部分时间”中。如果您不关闭数据库连接并将其留给GC来触发终结器,那么它将在单元测试中正常工作,并且在生产环境中部署时会严重中断。无论使用哪种语言,确定性清除都很重要。
Nemanja Trifunovic

8
@NemanjaTrifunovic:为什么要在实时数据库连接上进行单元测试?那不是一个单元测试。不,对不起,我不买。无论如何,您都不应在各处创建数据库连接,而应通过构造函数或属性来传递它们,这意味着您希望使用类似堆栈的自动破坏语义。实际上,依赖数据库连接的对象很少。如果非确定性清理经常让您如此痛苦,那么难,那是因为糟糕的应用程序设计,而不是糟糕的语言设计。
2011年

17

您对的漏洞的描述using不完整。考虑以下问题:

interface Bar {
    ...
}
class Foo : Bar, IDisposable {
    ...
}

Bar b = new Foo();

// Where's the Dispose?

我认为,同时没有RAII和GC是一个坏主意。当谈到在Java中关闭文件,这malloc()free()在那边。


2
我同意RAII是蜜蜂的膝盖。但是,该using子句是Java上C#迈出的重要一步。它确实允许确定性销毁并因此进行正确的资源管理(它不如您需要记住的那样好于RAII,但这绝对是个好主意)。
马丁·约克

8
“在Java中关闭文件时,在那儿是malloc()和free()。​​” –绝对如此。
康拉德·鲁道夫

9
@KonradRudolph:它比malloc和free差。至少在C语言中您没有例外。
Nemanja Trifunovic

1
@Nemanja:让我们说句公道话,你可以free()finally
DeadMG

4
@Loki:基类问题作为一个问题更为重要。例如,原始对象IEnumerable没有继承自IDisposable,并且有很多特殊的迭代器,因此永远无法实现。
DeadMG

14

我年纪大了 我去过那里,看到了它,并多次敲打我的头。

我当时在Hursley Park的一次会议上,IBM的男孩们在告诉我们这种全新的Java语言有多么奇妙,只有一个人问……为什么没有这些对象的析构函数。他并不是说我们知道C ++中的析构函数,但也没有终结器(或者有终结器,但它们基本上不起作用)。这已经过去了,那时我们认为Java只是一种玩具语言。

现在他们在语言规范中添加了终结器,Java得到了一些采用。

当然,后来所有人被告知不要在他们的对象上放置终结器,因为这极大地降低了GC的速度。(因为它不仅必须锁定堆,而且还必须将待完成的对象移动到临时区域,因为这些方法由于GC已暂停应用程序的运行而无法调用。而是在下一个应用程序之前立即调用它们GC周期)(更糟糕的是,有时在关闭应用程序时根本不会调用终结器。想像一下,永远都没有关闭文件句柄)

然后我们有了C#,我记得在MSDN上的讨论论坛上,我们被告知这种新的C#语言是多么的美妙。有人问为什么没有确定性的终结方法,MS的男孩们告诉我们我们不需要这些东西,然后告诉我们我们需要改变设计应用程序的方式,然后告诉我们GC有多么出色以及我们所有的旧应用程序如何垃圾,并且由于所有循环引用而从未使用过。然后他们屈服于压力,并告诉我们他们已将此IDispose模式添加到我们可以使用的规范中。我当时认为,对于我们在C#应用程序中的手动内存管理来说,这几乎可以追溯到。

当然,MS男孩后来发现,他们告诉我们的只是……好吧,他们使IDispose不仅仅是一个标准接口,而且后来添加了using语句。W00t!他们意识到,确定性最终确定毕竟是语言所缺少的。当然,您仍然需要记住将它放在任何地方,因此它仍然有点手动,但是效果更好。

那么,为什么从一开始就将使用样式的语义自动放置在每个作用域块上时,为什么要这样做呢?可能是效率,但我想认为它们只是没有意识到。就像最终他们意识到您仍然需要.NET(google SafeHandle)中的智能指针一样,他们认为GC确实可以解决所有问题。他们忘记了对象不仅仅是内存,而GC主要是设计用于处理内存管理的。他们陷入了GC将处理此问题的想法,并且忘记了您在其中放置了其他内容,一个对象不仅仅是一个内存块,如果您不删除它一会儿也没关系。

但我也认为,原始Java中缺少finalize方法的好处还不多-创建的对象都是关于内存的,以及是否要删除其他内容(如数据库句柄或套接字或其他内容) ),那么您应该手动进行操作

记住Java是为嵌入式环境设计的,在该环境中人们习惯于编写带有大量手动分配的C代码,因此不具有自动释放就不是什么大问题-他们以前从未这样做过,那么为什么在Java中需要它?问题与线程或堆栈/堆无关,它可能只是在这里使内存分配(并因此取消分配)更加容易。总之,try / finally语句可能是处理非内存资源的更好位置。

因此,恕我直言,.NET简单地复制Java最大的缺点的方法就是最大的缺点。.NET应该是更好的C ++,而不是更好的Java。


恕我直言,诸如“使用”块之类的东西是确定性清除的正确方法,但还需要做更多的事情:(1)确保对象的析构函数抛出异常时将其处置的一种方法;(2)一种自动生成例程方法以调用Dispose带有using指令的所有字段并指定是否IDisposable.Dispose应自动调用该方法的方法;(3)与相似的指令using,但仅Dispose在发生异常时才调用;(4)其中的一个变化IDisposable将带有Exception参数,并且...
超级猫

...将在using适当时自动使用;该参数将是nullusing块是否正常退出,或者如果它是通过异常退出的,则它将指示正在等待的异常。如果存在这种情况,则更有效地管理资源并避免泄漏会容易得多。
超级猫

11

布鲁斯·埃克尔(Bruce Eckel)是《用Java进行思考》和《用C ++进行思考》的作者,也是C ++标准委员会的成员,他认为,在许多领域(不仅仅是RAII),戈斯林和Java团队都没有做他们的事情。家庭作业。

...要了解该语言如何既令人讨厌又复杂,同时又经过精心设计,您必须牢记C ++所依赖的主要设计决策:与C的兼容性。看来,让大量C程序员迁移到对象的方法是使迁移透明化:允许他们在C ++下不变地编译C代码。这是一个巨大的约束,一直以来都是C ++的最大优势……及其祸根。这就是使C ++如此成功,如此复杂的原因。

它还欺骗了对C ++不够了解的Java设计人员。例如,他们认为操作员重载对于程序员来说很难正确使用。在C ++中,这基本上是正确的,因为C ++同时具有堆栈分配和堆分配,并且您必须使运算符重载以处理所有情况,并且不会导致内存泄漏。确实很难。然而,Java具有单一的存储分配机制和垃圾收集器,这使操作员的重载变得微不足道-就像C#所示(但早在Java之前的Python中就已显示)。但是多年来,Java团队的部分观点是“运算符重载太复杂了”。这个和其他许多人显然没有做出的决定

还有很多其他示例。“必须包含原始元素以提高效率”。正确的答案是忠于“一切都是对象”,并在需要提高效率时为进行低级活动提供陷阱(这也将使热点技术透明地提高效率,因为最终它们会有)。哦,这是您不能直接使用浮点处理器来计算先验函数的事实(而是由软件完成)。我已经尽我所能就这样的问题写过文章,而且我听到的答案一直是对“这就是Java方式”的效果的重言式回答。

当我写关于泛型设计的糟糕程度的文章时,我得到了同样的回答,以及“我们必须向后兼容Java以前做出的(糟糕的)决定。” 最近,越来越多的人对Generics有了足够的经验,发现它们真的很难使用-实际上,C ++模板功能更强大且更一致(由于可以容忍编译器错误消息,因此使用起来也更加容易)。人们甚至一直在认真地考虑修改问题,这可能会有所帮助,但不会在设计中受到自我强加约束的影响。

这份清单一直很乏味...


5
这听起来像是Java与C ++的答案,而不是专注于RAII。我认为C ++和Java是不同的语言,各有优缺点。同样,C ++设计人员没有在很多领域做功课(未应用KISS原理,缺少类的简单导入机制等)。但是问题的焦点是RAII:Java缺少此功能,您必须手动对其进行编程。
乔治

4
@Giorgio:文章的重点是,Java似乎在许多问题上错过了机会,其中一些问题与RAII直接相关。关于C ++及其对Java的影响,Eckels指出:“您必须牢记C ++所依赖的主要设计决策:与C的兼容性。这是一个巨大的约束,一直是C ++的最大优势……及其祸根。它还愚弄了对C ++不够了解的Java设计人员。” C ++的设计直接影响了Java,而C#则有机会从两者中学习。(是否这样做是另一个问题。)
Gnawme 2011年

2
@Giorgio研究特定范式和语言家族中的现有语言确实是新语言开发所需的家庭作业的一部分。这是一个他们只是简单地用Java编写的例子。他们要研究C ++和Smalltalk。C ++开发时没有Java。
杰里米

1
@Gnawme:“ Java似乎错过了很多问题,其中一些与RAII直接相关”:您能提到这些问题吗?您发布的文章没有提到RAII。
乔治

2
@Giorgio当然,自从C ++的开发以来,已经进行了许多创新,这些创新可以解决您所缺乏的许多功能。通过开发C ++之前建立的语言,他们应该找到那些功能吗?这就是我们正在用Java谈论的作业-他们没有理由不考虑Java开发中的每个C ++功能。有些人像他们故意遗漏的多重继承-有些人像RAII却被他们忽略了。
杰里米

10

最好的原因比这里的大多数答案要简单得多。

您不能将堆栈分配的对象传递给其他线程。

停下来想一想。继续思考...。现在,当每个人都对RAII如此热衷时,C ++就没有线程了。当您传递过多的对象时,甚至Erlang(每个线程有单独的堆)也会变得讨厌。C ++在C ++ 2011中只有一个内存模型。现在,您几乎可以在C ++中考虑并发性,而不必参考编译器的“文档”。

Java是从(几乎)第一天开始为多个线程设计的。

我仍然有“ C ++编程语言”的旧版本,其中Stroustrup向我保证我不需要线程。

第二个痛苦的原因是避免切片。


1
为多个线程而设计的Java还解释了为什么GC不基于引用计数的原因。
2011年

4
@NemanjaTrifunovic:您无法将C ++ / CLI与Jav​​a或C#进行比较,它的设计几乎是出于与非托管C / C ++代码互操作的明确目的。它更像是一种非托管语言,恰好可以访问.NET框架,反之亦然。
亚伦诺特,2011年

3
@NemanjaTrifunovic:是的,C ++ / CLI是如何以完全不适合常规应用程序的方式完成此操作的一个示例。这是唯一的C / C ++的互操作非常有用。普通开发人员不仅不必为完全不相关的“堆栈或堆”决定而烦恼,而且,如果您尝试对其进行重构,那么意外地创建空指针/引用错误和/或内存泄漏也很容易。抱歉,但是我想知道您是否曾经用Java或C#编程过,因为我认为没有真正想要 C ++ / CLI中使用的语义。
2011年

2
@Aaronaught:我已经用Java(少量)和C#(大量)进行了编程,而我当前的项目几乎全部是C#。相信我,我知道我在说什么,它与“堆栈与堆”无关-它与确保在不需要时立即释放所有资源有关。自动地。如果不是的话,您遇到麻烦。
Nemanja Trifunovic

4
@NemanjaTrifunovic:太好了,真的很棒,但是C#和C ++ / CLI都要求您明确声明何时要发生这种情况,它们只是使用了不同的语法。没有人会争辩您当前正在徘徊的基本要点(“不需要时立即释放资源”),但是您正在做出巨大的逻辑飞跃,“所有托管语言都应具有自动但仅基于调用栈的确定性处置”。它只是不积水。
亚伦诺特,2011年

5

在C ++中,您使用了更多通用的较低级语言功能(在基于堆栈的对象上自动调用了析构函数)来实现较高级的语言功能(RAII),而C#/ Java人士似乎并不喜欢这种方法。太喜欢了。他们宁愿为满足特定需求而设计特定的高级工具,然后将其提供给语言中内置的现成的程序员。这种特定工具的问题在于,通常无法对其进行自定义(部分原因是它们易于学习)。从较小的块构建时,随着时间的流逝,可能会出现更好的解决方案,而如果您具有高级内置结构,则这种可能性较小。

所以,是的,我认为(我实际上不在那儿……)这是一个明智的决定,目的是使语言更易于掌握,但我认为这是一个错误的决定。再说一次,我通常更喜欢C ++给程序员一个机会来展示他们自己的哲学,所以我有点偏颇。


7
“让程序员有机会滚动自己的哲学”可以很好地发挥作用,直到您需要结合程序员各自编写自己的字符串类和智能指针的程序员编写的库。
dan04 2011年

@ dan04,因此托管语言可以为您提供预定义的字符串类,然后让您对其进行猴子补丁处理,如果您是那种无法应对不同的自卷字符串的人,这就是灾难的秘诀类。
gbjbaanb 2013年

-1

您已经使用该Dispose方法在C#中调用了大致等效的代码。Java也有finalize注意: 我意识到Java的finalize是不确定的,并且与Dispose,我只是指出它们都具有在GC旁边清理资源的方法。

尽管有什么困难,但是C ++变得更加痛苦,因为必须物理破坏对象。在C#和Java之类的高级语言中,当不再有引用时,我们依靠垃圾收集器对其进行清理。不能保证C ++中的DBConnection对象没有流氓引用或指向它的指针。

是的,C ++代码阅读起来可能更直观,但是却可能成为调试的噩梦,因为Java之类的语言所施加的边界和局限性排除了一些更为棘手和棘手的错误,并保护了其他开发人员免受常见的菜鸟错误。

也许可以归结为偏好,例如C ++的低级功能,控制和纯正,而其他人(例如我自己)则更喜欢更明确的沙盒语言。


12
首先,Java的“ finalize”是不确定的……它等同于C#的“ dispose”或C ++的析构函数……而且,如果您使用.NET,C ++也会有一个垃圾收集器
JoelFan

2
@DeadMG:问题是,您可能不是一个白痴,但是刚离开公司(并写了您现在维护的代码)的其他人可能已经来了。
凯文

7
无论您做什么,那个家伙都会写糟糕的代码。你不能让一个糟糕的程序员让他写出好的代码。在与白痴打交道时,悬挂指针是我最不关心的问题。好的编码标准将智能指针用于必须跟踪的内存,因此,智能管理应该使如何安全地释放和访问内存变得显而易见。
DeadMG

3
DeadMG说了什么。关于C ++有很多坏事。但是RAII并不是其中之一。实际上,缺少Java和.NET来正确地进行资源管理(因为内存是唯一的资源,对吧?)是它们最大的问题之一。
康拉德·鲁道夫

8
我认为终结器是灾难设计的明智之举。当您迫使对象从设计者到对象的用户正确使用对象时(不是在内存管理方面,而是在资源管理方面)。在C ++中,类设计者有责任正确获得资源管理(仅执行一次)。在Java中,类用户有责任正确获得资源管理,因此每次使用该类时都必须完成。stackoverflow.com/questions/161177/...
马丁纽约
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.