是否有任何有效的用例使用new和delete,原始指针或带有现代C++的c风格数组?

use*_*042 37 c++ c++11

这是一个值得注意的视频(停止教学C)关于教授c ++语言的范式变化.

还有一篇值得关注的博文

我有一个梦想 ...

我梦想着所谓的C++课程/课程/课程将停止教学(要求)他们的学生使用:...

自从C++ 11成为既定标准以来,我们拥有动态内存管理功能,即智能指针.
即使从早期的标准,我们有c ++标准容器库作为原始数组(分配new T[])的良好替代品(特别是使用std::string而不是c风格的NUL终止字符数组).

问题以粗体显示:

抛开放置new覆盖,是否存在使用智能指针或标准容器无法实现的任何有效用例,但仅使用newdelete直接使用(当然,除了实现此类容器/智能指针类)?

有时传言(在这里这里)使用newdelete手动滚动对于某些情况可以"更有效".实际上这些是哪些?这些边缘情况是否需要以与标准容器或智能指针需要相同的方式跟踪分配?

原始c风格的固定大小数组几乎相同:std::array现在,它允许所有类型的分配,复制,引用等轻松和语法一致,如所有人所期望的那样.是否有任何用例选择T myArray[N];c风格的数组而不是std::array<T,N> myArray;


关于与第三方图书馆的互动:

假设第三方库返回使用newlike 分配的原始指针

MyType* LibApi::CreateNewType() {
    return new MyType(someParams);
}
Run Code Online (Sandbox Code Playgroud)

你总是可以将它包装成一个智能指针,以确保delete被调用:

std::unique_ptr<MyType> foo = LibApi::CreateNewType();
Run Code Online (Sandbox Code Playgroud)

即使API要求您调用其遗留函数来释放资源,例如

void LibApi::FreeMyType(MyType* foo);
Run Code Online (Sandbox Code Playgroud)

你仍然可以提供一个删除功能:

std::unique_ptr<MyType, LibApi::FreeMyType> foo = LibApi::CreateNewType();
Run Code Online (Sandbox Code Playgroud)

我对有效的"每天"用例特别感兴趣,与学术/教育目的要求和限制形成对比,这些要求和限制并未被上述标准设施所涵盖.
newdelete可以在存储器管理/垃圾收集器框架或标准容器实现中使用是毫无疑问的1.


一个主要动机......

...提出这个问题的方法是提供一种替代方法与任何(家庭作业)问题,这些问题仅限于使用标题中提到的任何结构,但是关于生产就绪代码的严重问题.

这些通常被称为内存管理的基础,这是IMO公然错误/被误解为适合初学者讲座和任务.


1)添加:关于该段,这应该是一个明确的指标,newdelete不是初学c ++学生,但应该留给更高级的课程.

Yak*_*ont 25

所有权不应该是本地的.

作为示例,指针容器可能不希望对其中的指针的所有权驻留在指针本身中.如果您尝试使用前向唯一ptrs编写链接列表,则在销毁时您可以轻松地清除堆栈.

具有vector指针的类似容器可能更适合于在容器或子容器级别存储删除操作,而不是在元素级别.

在那些和类似的情况下,你像智能指针一样包装所有权,但你在更高的层次上做.许多数据结构(图形等)可能具有类似的问题,其中所有权正确地驻留在比指针更高的位置,并且它们可能不直接映射到现有的容器概念.

在某些情况下,可能很容易从其余的数据结构中分解出容器所有权.在其他人可能没有.

有时你有非常复杂的非本地非参考计算生命周期.在这些情况下,没有合理的位置来放置所有权指针.

在这里确定正确性很难,但并非不可能.存在正确且具有如此复杂的所有权语义的程序.


所有这些都是极端情况,很少有程序员在职业生涯中遇到过几次.

  • 在问题中明确排除了实现容器. (5认同)

Jer*_*fin 14

我将成为逆向,并且继续记录说"不"(至少对于我确定你真的打算提出的问题,对于大多数被引用的案例而言).

看起来像使用的明显用例newdelete(例如,GC堆的原始内存,容器的存储)实际上并非如此.对于这些情况,您需要"原始"存储,而不是对象(或对象数组,分别是什么newnew[]提供).

由于您需要原始存储,因此您确实需要/想要使用operator newoperator delete管理原始存储本身.然后使用placement new在该原始存储中创建对象,并直接调用析构函数来销毁对象.根据具体情况,您可能希望使用间接级别 - 例如,标准库中的容器使用Allocator类来处理这些任务.这作为模板参数传递,其提供定制点(例如,基于特定容器的典型使用模式来优化分配的方式).

因此,对于这些情况,您最终会使用new关键字(在放置新位置和调用位置operator new),但不是类似的T *t = new T[N];,这是我非常确定您打算询问的内容.

  • @bipll:我没看到如何使用placement new暴露实现细节.您是否注意到使用此技术的现有容器(例如,矢量)的实现细节? (3认同)

Jes*_*uhl 11

一个有效的用例是必须与遗留代码进行交互.特别是如果将原始指针传递给拥有它们所有权的函数.

并非您使用的所有库都可能使用智能指针并使用它们,您可能需要提供或接受原始指针并手动管理它们的生命周期.如果它有很长的历史记录,那么在您自己的代码库中甚至可能就是这种情况.

另一个用例是必须与没有智能指针的C进行交互.

  • 这就是`release()`的用途,通过所有权. (5认同)
  • 这是`new`的半有效用例(如果遗留代码接受一个拥有的原始指针并在内部调用delete),但不是`delete`; 我认为做出这种区分非常重要.而且,即使是`new`,虽然它很冗长,但人们可以说最好写一下`make_unique <Foo>(..).release()`.这解决了您对NathanOliver的建议的担忧. (4认同)
  • 我不认为这是一个有效的用例。如果您需要获取原始指针,则可以使用“get”,并且可以使用自定义删除器来接受原始指针,并在它进入您的范围时正确处理它。 (2认同)

Nik*_* C. 5

某些API可能希望您创建对象,new但将接管对象的所有权.例如,Qt库具有父子模型,其中父项删除其子项.如果您使用智能指针,如果您不小心,则会遇到双重删除问题.

例:

{
    // parentWidget has no parent.
    QWidget parentWidget(nullptr);

    // childWidget is created with parentWidget as parent.
    auto childWidget = new QWidget(&parentWidget);
}
// At this point, parentWidget is destroyed and it deletes childWidget
// automatically.
Run Code Online (Sandbox Code Playgroud)

在这个特定的例子中,你仍然可以使用智能指针,它会很好:

{
    QWidget parentWidget(nullptr);
    auto childWidget = std::make_unique<QWidget>(&parentWidget);
}
Run Code Online (Sandbox Code Playgroud)

因为对象以声明的相反顺序销毁.unique_ptrchildWidget首先删除,这将使自己childWidget注销parentWidget,从而避免双重删除.但是,大多数时候你没有那种整洁.在许多情况下,父母将首先被销毁,在这种情况下,儿童将被删除两次.

在上述情况下,我们拥有该范围内的父母,因此可以完全控制情况.在其他情况下,父母可能不是小时,但我们将我们的子小部件的所有权交给那个居住在其他地方的父母.

您可能正在考虑要解决此问题,您只需要避免使用父子模型并在堆栈上创建所有小部件而不使用父级:

QWidget childWidget(nullptr);
Run Code Online (Sandbox Code Playgroud)

或者使用智能指针而没有父母:

auto childWidget = std::make_unique<QWidget>(nullptr);
Run Code Online (Sandbox Code Playgroud)

然而,这也会在你的脸上爆炸,因为一旦你开始使用小部件,它可能会在你的背后重新成为父母.一旦另一个对象成为父对象,在使用时会得到双删除unique_ptr,并在堆栈上创建堆栈时删除堆栈.

使用它的最简单方法是使用new.其他任何事情都要么是麻烦,要么是更多的工作,或两者兼而有之.

这些API可以在现代的,不推荐使用的软件(如Qt)中找到,并且早在智能指针出现之前就开发了很多年.它们不能轻易改变,因为这会破坏人们现有的代码.