与.net相比,C++的所有权概念

C++ concept of ownership, compared to .net

本文关键字:所有权 C++ net 相比      更新时间:2023-10-16

下面是我从当前有关该主题的知识中得出的一系列结论,问题本质上是这是否正确,如果不正确,对这些结论的适当更正是什么。

作为一名经验丰富的.net开发人员,我完全相信所有对象实例几乎都是作为云中的粒子存在的,而对象成员身份只是将这些粒子相互关联。当粒子或粒子云无法通过某种引用链接回框架中的根对象时,它或其成员将被丢弃。这本质上是引用计数和某种网络分析的结果,以保持对对象是否仍有通往根的路径的理解。

在这种结构中,一个对象的可识别实例可以被许多其他对象引用("拥有"),并且所有权网络可以是必要的复杂和循环/自引用。

在向C++过渡的过程中,为了内存管理的好处,必须限制这种自由度。所有对象都必须有一个明确的所有权树,对象的生存期由其父对象维护。

对于支撑代码段{}中的临时值,生存期也可能受到范围的限制。通过使用new关键字和小心使用delete,也可以避免使用寿命

在C++的新标准化中,像shared_ptr这样的东西似乎是为了允许更接近.net托管内存模型。我不知道这些是否也提供了托管内存的相同好处,即丢弃自引用但在其他方面不连接的对象云。

std::列表就是一个例子。据我所知,推荐的策略是,在没有shared_ptr构造的情况下,列表中的对象实例必须由列表单独拥有,并且其生存期由列表自己的生存期决定。这导致需要复制构造函数、临时调用的析构函数,或者使用需要列表具有单个具体类型的模板方法。替代的解决方案包括存储指向对象的指针,并在其他地方管理对象的生存期,尽管这充满了明显的危险。这一切看起来都很尴尬。

在.net中,一个列表可以引用一个对象,而不必是它的父对象或监护人,因为该对象的生存期是以其他方式管理的。

我知道堆和堆栈内存之间的区别会对性能产生影响,但我对这个主题并不熟悉。

我对这两种制度的看法基本正确吗?如果没有,可以提供哪些更正?如果它本质上是正确的,那么有哪些文献描述了C++的心理模型,我需要采用这些模型来创建大型、强大和高性能的应用程序?这包括代码的最佳实践,以及稳健管理大型应用程序的更抽象的概念。

在(非垃圾收集的)C++中,内存管理是通过析构函数而不是一些垃圾收集器来完成的。这样做的主要优点是:

(1) 它非常精确。该语言对何时调用析构函数以及以何种顺序释放内存做出了非常有力的保证。

(2) 它简单、透明、完全可移植,开销非常小。与其依赖于一些不透明的垃圾收集机制,这种机制可以随心所欲地进行操作,不如确切地知道内存管理将如何在您编译的每个系统上工作。如果您不确定什么时候/是否释放了东西,可以将调试输出放入析构函数中并进行检查。的确,现代垃圾收集器非常好,很少会引起问题,但如果你真的遇到过垃圾收集器的问题,需要调试它,你就会知道这可能非常耗时和痛苦。

我知道堆和堆栈内存之间的区别对性能有影响,但我对这个主题并不熟悉。

堆和堆栈正是它们在C中的样子。堆栈的基本思想是,它是一块内存,用于存储程序的函数callstack。堆栈被布置为一系列"堆栈框架"。当一个函数被调用时,一个指针被按下,当我们稍后返回时,它会告诉我们应该返回的位置。函数调用的所有参数都依次放置在堆栈中。还有一个计数器/指针,指示这个堆栈帧有多大。当一个函数被调用时,一个新的堆栈帧会继续,当函数返回时,我们会弹出它。函数的局部变量也会在堆栈上分配。堆栈有一个固定的大小,如果超过它(通常是无限递归),就会出现堆栈溢出错误。堆栈上的所有内容都必须在编译时具有已知的固定大小。这是由"sizeof"运算符决定的。如果你想要一个动态数组,你必须使用"堆",它允许在运行时才知道大小的情况下分配内存,并且可以按任意顺序销毁。

堆栈的优点是内存分配基本上是即时的(堆栈帧指针只是被移动),并且释放以一种非常舒适的方式发生。(当我们从函数返回,而本地变量超出范围时,我们只是从堆栈中弹出以释放它们的内存。)C++对堆栈分配的对象和析构函数做出了非常有力的保证。保证是,当函数返回时,析构函数将始终以创建的相反顺序被调用,即使它抛出异常而不是正常返回。从本质上讲,除非程序异常终止(std::terminate()throw 42),或者除非发生一些不寻常的事情(在某些特殊情况下,析构函数本身会抛出异常,但基本上你永远不应该这样做,因为这会带来麻烦),否则自动对象的析构函数将在非常特定的时间点以非常特定的顺序调用。如果它们有带构造函数/析构函数的成员变量,那么这些变量也会按特定顺序被调用。如果涉及继承。。。如果你不熟悉,你可以读一下。

基本上,这是一种非常强大和严格的机制,您可以使用它来控制各种资源——不仅是内存,还有套接字、到打印机的连接、指向必须"关闭"的C库实例的指针、任何独占的共享资源,或者在不再需要时必须以某种方式清理的东西。

不是所有的东西都可以放在堆栈上。例如,如果你有一个动态大小的数组,它不能放在堆栈上,它必须放在堆上,就像C中通常教授的那样,(在C++中使用运算符new)它必须与一个要删除的调用配对,否则内存不会被释放,析构函数也不会被调用。

在C++中,最好的管理方式是利用堆栈的功能。您不用自己手动调用delete,而是使用堆栈分配的对象,该对象的析构函数调用delete。最简单的例子是"std::unique_ptr"(又名boost::scope_ptr)。您提到的shared_ptr也执行类似的操作。所有的C++标准容器,如vector、list、map等也都这样做。

IMO这个模式基本上就是C++的福音。在一个编写良好的C++程序中,不仅所有内存,而且所有资源都以这种方式管理,由析构函数释放。这就是非常重要的RAII习语:"资源获取就是初始化"。您可以在此处阅读更多信息:http://www.c2.com/cgi/wiki?ResourceAcquisitionIsInitialization

在许多程序中,所有对象都只有一个所有者,而对象所有权图是一棵树。在这种情况下,所有的东西都可以是堆栈分配的/堆栈分配的成员变量,然后所有的内存都打开并由堆栈管理。在某些情况下,对象需要共享所有权——所有权图是一个DAG(有向无环图)。然后,您可以对共享对象使用shared_ptr。它们将在堆上分配,但它将自动工作,内存将得到管理,而无需再做任何事情。在最复杂的情况下,有循环引用,即所有权图中的循环。如果在这种情况下只使用shared_ptr,那么循环就不会被释放。这种情况下,垃圾收集器应该为你"做艰苦的工作"。在现代C++中,首选的方法是在大多数地方仍然使用shared_ptr,但在任何循环的至少一个链接上,都可以使用weak_ptr,这样循环就"被破坏"了,可以自动释放。这很少是必要的,当它是,它真的没有那么多的工作。它只是许多其他语言的自动垃圾收集的一种替代工程风格。

您的描述有效,但看起来有些奇怪,因为它本质上试图定义对象生存期概念,因为的概念对于ISO C++和.net都是相同的。

事实上。。。事实并非如此。因此,您的分析适用于C++的子部分,该子部分处理基于堆的对象,而C++则更多。

在.net中,像intdouble这样的东西不是正确的对象(它们被称为值,并且只有当装箱时才被视为对象)。

.net(和C#)在值类(struct-s)和

引用类考虑

b=a; a.val+=2;

b.val的值是多少?

C++中的值/引用的概念与类的概念无关。唯一的纠缠是当涉及运行时多态性时。

CCD_ 8和CCD_。int-s和person-s仅由列表所有,因为它们在概念上是列表的成员

person a("john"), b("dave");
a = b;
b.name="robert";

实际上会让a.name保留"dave"。

std::list<shared_ptr<person> >

另一个故事是:列表拥有ptr,而ptr又拥有这个人。列表(独占所有者)的策略适用于其策略(共享)适用于个人的指针。

要匹配与java或c#类似的语义,您应该使用这样的语义技巧

class person_ { ... };
typedef std::shared_ptr<person_> person;
std::list<person> ...

现在,这就像使用引用类(除了使用->而不是.)执行aperson->name = "...",该人的所有引用都将看到新名称。

还请注意,shared_ptr只是引用计数指针。循环引用不会被丢弃。没有一个指针网络是后台进程试图跟踪以发现不可达性的。C++没有任何垃圾收集器。