首页 文章

我什么时候应该在C中使用new关键字?

提问于
浏览
237

我've been using C++ for a short while, and I'一直在想新关键字 . 简单地说,我应该使用它吗?

1)使用新关键字...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";

2)没有新的关键字......

MyClass myClass;
myClass.MyField = "Hello world!";

从实现的角度来看,它们看起来并没有什么不同(但我确定它们是......)但是,我的主要语言是C#,当然第一种方法就是我习惯的方法 .

困难似乎是方法1更难与std C类一起使用 .

我应该使用哪种方法?

更新1:

我最近为堆内存(或免费存储)使用了new关键字,用于超出范围的大型数组(即从函数返回) . 在我使用堆栈之前,导致一半元素在范围之外被破坏,切换到堆使用确保了元素是完整的 . 好极了!

更新2:

我的一位朋友最近告诉我,使用 new 关键字有一个简单的规则;每次键入 new 时,请键入 delete .

Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

这有助于防止内存泄漏,因为您总是必须将删除放在某处(即,当您将其剪切并粘贴到析构函数或其他方式时) .

11 回答

  • 1

    第二种方法在堆栈上创建实例,以及声明为 int 的东西以及传递给函数的参数列表 .

    第一种方法为堆栈上的指针腾出空间,您已将其设置为内存中已在堆上分配新 MyClass 的位置 - 或免费存储 .

    第一种方法还要求 delete 使用 new 创建的内容,而在第二种方法中,当类超出范围(通常是下一个右大括号)时,类会自动被破坏并释放 .

  • 0

    Method 1 (using new)

    • free store上的对象分配内存(这通常与 heap 相同)

    • 要求您稍后明确 delete 您的对象 . (如果不删除它,可能会造成内存泄漏)

    • 内存保持分配,直到你 delete 它 . (即你可以 return 使用 new 创建的对象)

    • 除非指针是 delete d,否则问题中的示例将为leak memory;并且它 should always be deleted ,无论采用哪种控制路径,或者抛出异常 .

    Method 2 (not using new)

    • stack 上的对象分配内存(其中所有局部变量都去)通常可用于堆栈的内存较少;如果分配了太多对象,则存在堆栈溢出的风险 .

    • 稍后你不需要 delete .

    • 超出范围时不再分配内存 . (即你不应该 return 指向堆栈上对象的指针)

    至于哪一个使用;鉴于上述限制,您选择最适合您的方法 .

    Some easy cases:

    • 如果您不想担心调用 delete ,(并且可能导致memory leaks),则不应使用 new .

    • 如果您想从函数返回指向对象的指针,则必须使用 new

  • 8

    两者之间存在重要差异 .

    未分配 new 的所有内容都与C#中的值类型非常相似(人们常说这些对象是在堆栈上分配的,这可能是最常见/最明显的情况,但并非总是如此 . 更确切地说,不使用 new 分配的对象有自动存储持续时间使用 new 分配的所有内容都在堆上分配,并返回指向它的指针,与C#中的引用类型完全相同 .

    在堆栈上分配的任何东西都必须具有一个常量大小,在编译时确定(编译器必须正确设置堆栈指针,或者如果对象是另一个类的成员,则必须调整其他类的大小) . 这就是C#中的数组是引用类型的原因 . 它们必须是,因为使用引用类型,我们可以在运行时决定要求多少内存 . 这同样适用于此 . 只有具有常量大小(可在编译时确定的大小)的数组才能分配自动存储持续时间(在堆栈上) . 必须通过调用 new 在堆上分配动态大小的数组 .

    (这就是与C#有任何相似之处的地方)

    现在,在堆栈上分配的任何内容都有"automatic"存储持续时间(您实际上可以将变量声明为 auto ,但如果没有指定其他存储类型,则这是默认值,因此实际上并未真正使用该关键字,但这就是它的来源从)

    自动存储持续时间意味着它的声音,自动处理变量的持续时间 . 相比之下,堆上分配的任何内容都必须由您手动删除 . 这是一个例子:

    void foo() {
      bar b;
      bar* b2 = new bar();
    }
    

    此函数创建三个值得考虑的值:

    在第1行,它在堆栈上声明了 bar 类型的变量 b (自动持续时间) .

    在第2行,它在堆栈上声明 bar 指针 b2 (自动持续时间),并调用new,在堆上分配 bar 对象 . (动态持续时间)

    当函数返回时,将发生以下情况:首先, b2 超出范围(破坏的顺序始终与构造顺序相反) . 但是 b2 只是一个指针,所以没有任何反应,它所占用的内存就被释放了 . 而且重要的是,它所指向的内存(堆上的 bar 实例)没有被触及 . 仅释放指针,因为只有指针具有自动持续时间 . 其次, b 超出范围,因此它具有自动持续时间,因此调用其析构函数,并释放内存 .

    堆上的 bar 实例?它's probably still there. No one bothered to delete it, so we'已经泄露了记忆 .

    从这个例子中,我们可以看到任何具有自动持续时间的东西都保证在它超出范围时调用它的析构函数 . 那很有用 . 但是在堆上分配的任何内容都会持续到我们需要的时间,并且可以动态调整大小,就像数组一样 . 这也很有用 . 我们可以用它来管理我们的内存分配 . 如果Foo类在其构造函数中在堆上分配了一些内存,并在其析构函数中删除了该内存,该怎么办?然后我们可以充分利用两个世界,安全的内存分配,保证再次释放,但没有强制一切都在堆栈上的限制 .

    这几乎是大多数C代码的工作原理 . 例如,查看标准库的 std::vector . 这通常在堆栈上分配,但可以动态调整大小和调整大小 . 它通过在内部根据需要在堆上分配内存来实现 . 该类的用户永远不会看到这一点,因此没有机会泄漏内存,或者忘记清理你分配的内容 .

    这个原则称为RAII(资源获取是初始化),它可以扩展到必须获取和释放的任何资源 . (网络套接字,文件,数据库连接,同步锁) . 所有这些都可以在构造函数中获取,并在析构函数中释放,因此您可以保证获得的所有资源都将被释放 .

    作为一般规则,永远不要直接从高级代码中使用new / delete . 始终将它包装在一个可以为您管理内存的类中,这将确保它再次被释放 . (是的,此规则可能有例外 . 特别是,智能指针要求您直接调用 new ,并将指针传递给其构造函数,然后接管并确保正确调用 delete . 但这仍然是一个非常重要的规则拇指)

  • 108

    我应该使用哪种方法?

    这几乎不是由您的输入首选项决定的,而是由上下文决定的 . 如果您需要将对象保留在几个堆栈中,或者如果它对于堆栈来说太重,则将其分配给免费存储 . 此外,由于您正在分配对象,因此您还负责释放内存 . 查找 delete 运算符 .

    为了减轻使用免费商店管理的负担,人们发明了诸如 auto_ptrunique_ptr 之类的东西 . 我强烈建议你看看这些 . 他们甚至可能对您的打字问题有所帮助;-)

  • 1

    如果你是用C语写的,那么你可能正在为性能写作 . 使用new和free存储比使用堆栈要慢得多(特别是在使用线程时),所以只在需要时使用它 .

    正如其他人所说,当对象需要在函数或对象范围之外生存时,对象非常大或者在编译时不知道数组的大小时,需要新的 .

    另外,尽量避免使用删除 . 将您的新内容包装成智能指针 . 让智能指针调用为您删除 .

    在某些情况下智能指针不智能 . 永远不要将std :: auto_ptr <>存储在STL容器中 . 由于容器内的复制操作,它将很快删除指针 . 另一种情况是你有一个非常大的STL容器指向对象的指针 . boost :: shared_ptr <>会有大量的速度开销,因为它会上下调整引用计数 . 在这种情况下更好的方法是将STL容器放入另一个对象,并为该对象提供一个析构函数,该析构函数将在容器中的每个指针上调用delete .

  • 0

    简短的回答是:如果你是C的初学者,你永远不应该自己使用 newdelete . 相反,您应该使用智能指针,例如 std::unique_ptr (或更少, std::shared_ptr ) . 这样,你就不会更高级,最佳做法通常是将你使用 newdelete 的自定义方式封装到一个专门用于对象生命周期问题的小类(例如自定义智能指针)中 .

    当然,在幕后,这些智能指针仍在执行动态分配和释放,因此使用它们的代码仍然会产生相关的运行时开销 . 这里的其他答案已经涵盖了这些问题,以及如何制定关于何时使用智能指针的设计决策,而不仅仅是在堆栈上创建对象或将它们作为对象的直接成员合并,以至于我不会重复它们 . 但我的执行摘要是:不要使用智能指针或动态分配,直到某些东西强迫你 .

  • 1

    没有 new 关键字,您将其存储在call stack上 . 在堆栈上存储过大的变量将导致stack overflow .

  • 2

    如果您的变量仅在单个函数的上下文中使用,则最好使用堆栈变量,即选项2.正如其他人所说,您不必管理堆栈变量的生命周期 - 它们是自动构造和破坏 . 此外,通过比较,在堆上分配/取消分配变量的速度很慢 . 如果你的函数经常被调用,那么如果使用堆栈变量和堆变量,你会看到巨大的性能提升 .

    也就是说,有一些显而易见的堆栈变量不足的实例 .

    如果堆栈变量的内存占用量很大,则存在溢出堆栈的风险 . 默认情况下,Windows上为the stack size of each thread is 1 MB . 您不太可能创建一个大小为1 MB的堆栈变量,但您必须记住堆栈利用率是累积的 . 如果你的函数调用一个调用另一个调用另一个函数的函数的函数,那么所有这些函数中的堆栈变量占用同一堆栈的空间 . 递归函数可以快速遇到此问题,具体取决于递归的深度 . 如果这是一个问题,您可以增加堆栈的大小(不推荐)或使用new运算符在堆上分配变量(推荐) .

    另一个更可能的条件是你的变量需要“活”超出你的函数范围 . 在这种情况下,您将在堆上分配变量,以便可以在任何给定函数的范围之外到达它 .

  • 268

    你是否正在将myClass传递出一个函数,或者期望它存在于该函数之外?正如其他人所说,当你没有在堆上分配时,它就是范围 . 当你离开这个功能时,它会消失(最终) . 初学者犯的一个经典错误是尝试在函数中创建某个类的本地对象并返回它而不将其分配到堆上 . 我记得在早些时候做c调试这种事情 .

  • 3

    简单的答案是肯定的 - new()在堆上创建一个对象(带有不幸的副作用,你必须管理它的生命周期(通过显式调用delete),而第二个表单在当前堆栈中创建一个对象范围和该对象在超出范围时将被销毁 .

  • 13

    简短的回答是肯定的,“new”关键字非常重要,因为当你使用它时,对象数据存储在堆上而不是堆栈,这是最重要的!

相关问题