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

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


具有C ++等效项:

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


这意味着在C ++中,无需记住将诸如DbConnection之类的资源包含在using块中!这似乎是C ++的一大优势。当您考虑一个类具有DbConnection类型的实例成员的类时,这甚至更令人信服,例如,

class Foo {
    DbConnection dbConn;

    // ...
}


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

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的重要性?)

评论

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

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

我的理解是,只有C ++编译器足够好以至于可以实际使用高级模板时,才可以理解RAII作为一种策略,这在Java之后是很不错的。创建Java时实际上可用的C ++是一种非常原始的“带有类的C”样式,如果可以的话,可能带有基本的模板。

“我的理解是,只有当C ++编译器足够好以至于可以实际使用高级模板时,才可以理解RAII作为一种策略,这在Java之后是很早的。” -那不是真的。从一开始,构造器和析构器就一直是C ++的核心功能,远远早于模板的广泛使用以及Java的出现。
@JimInTexas:我认为肖恩在某处有一个基本的真理种子(尽管不是模板,但关键是例外)。构造函数/析构函数从一开始就存在,但是重要性和RAII的概念最初(我要寻找的词)并没有意识到。在我们意识到整个RAII至关重要的情况下,编译器花了好几年的时间才能变得很好。

#1 楼


现在查看C#及其Java根源,我想知道... Java开发人员是否完全欣赏他们放弃堆栈而转而使用堆而放弃RAII时所放弃的一切?
(同样,Stroustrup完全了解RAII的重要性吗?)

我很确定Gosling在设计Java时没有得到RAII的重要性。在采访中,他经常谈到遗漏泛型和运算符重载的原因,但从未提及确定性析构函数和RAII。
很有趣,即使Stroustrup在设计它们时也没有意识到确定性析构函数的重要性。我找不到报价,但是如果您真的喜欢它,可以在他的采访中找到它:http://www.stroustrup.com/interviews.html

评论


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

–user7043
11年7月7日在16:49

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

–user7043
2011年11月7日17:10



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

–马丁·约克
2011年11月7日17:54

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

–user7043
2011年11月7日在18:04



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

–马丁·约克
2011年11月7日在18:14

#2 楼

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

首先,基于对象是基于堆栈还是基于堆的对象不同语义的想法肯定违背了统一设计目标。两种语言都可以减轻程序员的负担。

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

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

在C#中,您拥有using关键字,该关键字在后来成为C#1.0的设计中排在很晚。整个IDisposable都是一件令人不快的事情,一个人想知道让using与C ++析构函数语法~一起工作可以标记那些可以自动应用样板IDisposable模式的类是否更为优雅?

评论


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

– JoelFan
2011年11月7日在18:48

C ++ / CLI具有非常不同的一组设计决策和约束。其中一些决定意味着您可以要求程序员更多地考虑内存分配和性能方面的问题:整个过程“付出足够的努力来使自己挂起”。而且我认为C ++ / CLI编译器比C#复杂得多(尤其是在早期)。

–拉里·奥布莱恩(Larry OBrien)
2011年11月7日在20:48

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

– BlueRaja-Danny Pflughoeft
2011年11月7日在20:58

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

–拉里·奥布莱恩(Larry OBrien)
2011年11月7日在22:59

@拉里:我同意。 C ++ / CLI确实使用〜作为IDisposable.Dispose()的语法糖,它比C#语法方便得多。

– dan04
2011年11月8日在1:59

#3 楼

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

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

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

,因为它使语言更加简单。
/>

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

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

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

GC不能确定性吗?

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

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

您可以说Java选择优化普通情况(内存),但是却浪费了不可替代的资源,例如文件和插座。今天,鉴于在C ++中采用了RAII,这似乎是错误的选择。但是请记住,Java的大多数目标受众是用来显式关闭这些内容的C(或“带类的C”)程序员。

那么C ++ / CLI“堆栈对象”呢? br />
它们只是Dispose(原始链接)的语法糖,就像C#using一样。但是,它不能解决确定性销毁的一般问题,因为您可以创建匿名gcnew FileStream("filename.ext"),而C ++ / CLI不会自动处置它。

评论


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

– BlueRaja-Danny Pflughoeft
2011年11月8日23:11



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

–超级猫
2012年7月13日在1:29

“不需要复制构造函数”听起来不错,但在实践中失败很严重。 java.util.Date和Calendar也许是最臭名昭著的例子。没有什么比新的Date(oldDate.getTime())更漂亮了。

–kevin cline
13年5月15日在3:35



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

– jwenting
13年5月16日在8:41

#4 楼

Java7引入了与C#using类似的东西:try-with-resources语句


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


所以我想他们不是有意识地选择不实现RAII还是他们同时改变了主意。

评论


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

– FrustratedWithFormsDesigner
2011年11月7日19:03



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

– BlueRaja-Danny Pflughoeft
2011年11月7日20:52



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

– jprete
2011年11月7日在22:06

-1,表示使用/尝试使用的资源与RAII不同。

– Sean McMillan
2011年11月7日22:35

@Sean:同意。使用它的地方离RAII还很远。

– DeadMG
2011年11月8日,0:29

#5 楼

Java特意没有基于堆栈的对象(即值对象)。这些是使对象在方法结束时自动销毁所必需的。
由于这个原因以及Java被垃圾收集的事实,确定性的确定或多或少是不可能的(例如,如果我“本地”对象在其他地方被引用了吗?然后在方法结束时,我们不希望它被破坏)。
但是,这对我们大多数人来说都很好,因为几乎不需要确定性的终结处理,除非与本机(C ++)资源进行交互!

为什么Java没有基于堆栈的对象?
(除基元之外。)
因为基于堆栈的对象的语义与基于堆的引用。想象一下下面的C ++代码;
return myObject;


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

现在该怎么办?
return myObject;


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

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

评论


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

– JoelFan
2011年11月7日23:36

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

– Nemanja Trifunovic
11年8月8日在1:44

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

– BlueRaja-Danny Pflughoeft
2011年11月8日17:46

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

– Nemanja Trifunovic
2011年11月9日下午13:52

@NemanjaTrifunovic:为什么要在实时数据库连接上进行单元测试?那不是一个单元测试。不,对不起,我不买。无论如何,您都不应在各处创建数据库连接,而应通过构造函数或属性来传递它们,这意味着您不需要像堆栈一样的自动销毁语义。实际上,几乎没有依赖数据库连接的对象应该拥有它。如果非确定性清理经常让您如此痛苦,那么难,那是因为不良的应用程序设计,而不是不良的语言设计。

– Aaronaught
2011年11月9日13:56



#6 楼

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

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

Bar b = new Foo();

// Where's the Dispose?


在我看来,没有RAII和GC都不是一个好主意。关于关闭Java中的文件,在那儿是malloc()free()

评论


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

–马丁·约克
2011年11月7日17:58



“在关闭Java文件时,在那儿是malloc()和free()。​​” –绝对。

–康拉德·鲁道夫(Konrad Rudolph)
2011年11月7日17:59

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

– Nemanja Trifunovic
2011年11月7日在18:52

@Nemanja:公平地说,您可以在最后使用free()。

– DeadMG
2011年11月7日,19:10

@Loki:作为问题,基类的问题更为重要。例如,原始的IEnumerable不能从IDisposable继承,并且有一堆特殊的迭代器永远无法实现。

– DeadMG
2011年11月7日,19:10

#7 楼

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

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

现在,他们在语言规范中添加了Finaliser,并且Java得到了一些采用。

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

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

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

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

但是我还认为,原始Java中缺少finalize方法还有一点点-您创建的对象都是关于内存的,如果要删除其他内容(例如DB句柄,套接字或其他任何东西),则您应该手动进行操作。

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

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

评论


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

–超级猫
2012年7月13日在1:34

...将在适当情况下通过自动使用;如果using块正常退出,则该参数将为null;否则,如果通过异常退出,则该参数将指示正在处理的异常。如果存在这种情况,则更有效地管理资源并避免泄漏会容易得多。

–超级猫
2012年7月13日在1:36

#8 楼

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


...要了解语言既令人讨厌又复杂,并且同时进行了精心设计,则必须牢记所有内容的主要设计决策。 C ++挂了:与C的兼容性。Stroustrup决定-如此看来,正确的是-让大量C程序员转移到对象的方法是使转移透明:允许他们不变地编译C代码在C ++下。这是一个巨大的限制,一直以来都是C ++的最大优势……及其祸根。这就是使C ++如此成功和如此复杂的原因。
还愚弄了对C ++不够了解的Java设计人员。例如,他们认为操作员重载对于程序员来说很难正确使用。在C ++中,这基本上是正确的,因为C ++同时具有堆栈分配和堆分配,并且您必须使运算符重载以处理所有情况,并且不会导致内存泄漏。确实很难。但是,Java具有单一的存储分配机制和垃圾收集器,这使操作员的重载变得微不足道-如C#所示(但早在Java之前的Python中就已显示)。但是,多年来,Java团队的部分口号是“操作员重载太复杂了”。有人显然没有做作业的这个决定以及许多其他决定,就是为什么我以拒绝Gosling和Java团队做出的许多选择而闻名。

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

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

清单继续到一点乏味的地方...


评论


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

–乔治
2011年11月7日在20:15

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

– Gnawme
11年7月7日在20:29

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

–杰里米
2011年11月7日20:33

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

–乔治
11年7月7日在21:22

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

–杰里米
11年8月8日在18:24

#9 楼

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

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

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

(几乎)从一开始就为多线程设计Java。

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

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

评论


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

– dan04
2011年11月8日,在1:01

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

– Aaronaught
2011年11月8日在20:58

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

– Aaronaught
2011年11月9日14:05



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

– Nemanja Trifunovic
2011年11月9日14:19

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

– Aaronaught
2011年11月9日15:14



#10 楼

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

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

评论


“让程序员有机会滚动自己的哲学”的工作原理很好,直到您需要结合由程序员编写的库,每个程序员都滚动自己的字符串类和智能指针。

– dan04
2011年11月8日,0:18

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

– gbjbaanb
13年9月9日在18:17

#11 楼

您已经使用Dispose方法在C#中调用了与此等效的粗略方法。 Java也有finalize。注意:我意识到Java的finalize是不确定的,并且不同于Dispose,我只是指出它们都具有与GC一起清理资源的方法。

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

是的,C ++代码可以更直观地读取,但由于边界和边界的限制,可能成为调试的噩梦。 Java之类的语言所具有的局限性排除了一些更为棘手和棘手的错误,并保护了其他开发人员免受常见的菜鸟错误的影响。

它可能归结为偏好,例如低级偏好C ++的强大功能,控制能力和纯净性,其他像我这样的人则喜欢更沙盒化的语言,这种语言更加明确。

评论


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

– JoelFan
2011年11月7日15:20

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

–凯文
2011年11月7日在16:36

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

– DeadMG
2011年11月7日17:06

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

–康拉德·鲁道夫(Konrad Rudolph)
2011年11月7日17:57



我认为终结器是灾难设计的明智之举。正如您所迫,从设计人员到对象用户都必须正确使用对象(不是在内存管理方面,而是在资源管理方面)。在C ++中,类设计者有责任正确获得资源管理(仅执行一次)。在Java中,类用户有责任正确获得资源管理,因此每次使用该类时都必须完成。 stackoverflow.com/questions/161177/…

–马丁·约克
2011年11月7日在18:05