在C++中实例化对象的正确方法是什么?

6

在C++(我使用QT)中,我可以通过以下两种方式创建QString类的实例:

方法1

QString str = "my string";

方法二

QString *str = new QString("my string");

我知道这与指针有关。所以我的问题是:
  1. 这两者之间的区别是什么?
  2. 我应该坚持使用哪种方法?
  3. 何时正确地使用方法1,何时正确地使用方法2?
  4. 在方法2中,我可以通过调用 delete str; 来销毁对象。使用方法1时如何删除 str 变量?
谢谢

3
  1. 当你可以的时候
  2. 当1不是一个选项时
- parapura rajkumar
3
始终使用方法1,甚至使用QString str("my string");忘记指针,newdelete的所有内容。大多数情况下,它们都不是正确的工具。 - Kerrek SB
https://dev59.com/jXRC5IYBdhLWcg3wG9Nb - Don Reba
@KerrekSB 你为什么这么说? - lapk
6个回答

5
  1. 两种方法主要的不同在于它们的生命周期:在第二种方法中创建的对象将一直存在,知道调用 delete 方法;而在第一种方法中,它将在函数调用后自动销毁(如果有)。此外,由于涉及到非平凡的内存管理,第二种方法需要更多的工作。

  2. 使用符合你所需生命周期的方法。如果第一种方法的寿命已经足够,请不要使用第二种方法,因为它会增加内存管理的开销。如果你可以重构程序并同时改进设计,那将更加高效和优雅。

  3. 请参考上面的第二条。尤其是,使用第一种方法并存储指向该对象的指针并在其生命周期结束后访问它,是一个陷阱。虽然使用第二种方法也可能出现这种情况,但显式的销毁使程序员更加注意它(但仍然因为生命周期不明确,这是一个常见的陷阱)。第二种方法的陷阱是忘记删除它,导致内存泄漏(或过早地删除它并像前面一段描述的一样引用它)。

  4. 在第一种方法中,当函数返回时,对象将被自动删除。


如果我将使用方法1创建的对象作为函数结果返回,会发生什么?那么我需要释放它还是编译器足够聪明以知道何时应该释放它? - Roman
1
如果你这样做,你的程序会出现重大错误,因为编译器会在函数返回结果时释放它,无论你是否将其作为结果返回,所以你将返回一个指向无处的指针。 - necromancer
2
假设您返回对象的指针。如果您返回对象本身...将会创建一个副本。 - Daniel Placek
@agksmehx 的寿命与通常复制的对象相同-这取决于它的使用方式。 - Seth Carnegie
1
@agksmehx,指向对象p的生命周期将在该语句结束时结束(对象将在语句结束时被销毁)。QString q = f()QString q(f());相同,因此会构造一个临时对象并传递给QString的复制构造函数,然后在语句结束时销毁该临时对象。但是,编译器可以自由地优化掉它。 - Seth Carnegie
显示剩余2条评论

5
两者是非常不同的东西。 QString str("my string"); 创建一个对象,其生命周期是自动管理的:如果它是局部变量,则该对象存在于其封闭作用域的结尾,如果它是全局变量,则该对象存在于程序的结尾。 new QString("my string"); 创建一个具有手动管理生命周期的对象,也称为“动态对象”(并返回指向该对象的指针)。这意味着负责管理对象的生命周期。这几乎从不是正确的做法,除非您正在编写库组件。
这就是C++哲学的核心所在:C++是一种用于编写库的语言。您拥有编写高质量、可重复使用组件的工具。如果您这样做了,您将需要了解生命周期管理的复杂性。然而,在此之前,您应该使用现有的库组件。在这样做时,您几乎永远不需要执行任何手动管理。
使用动态容器(向量、字符串、映射等)存储数据并构建自己的数据结构。如果需要修改调用者作用域中的对象,请通过引用传递参数。从简单组件构建复杂类。如果您真的需要动态对象,请通过unique_ptr<T>shared_ptr<T>处理程序类来处理它们。
不要使用指针。很少使用new,永远不要使用delete(进一步的提示:1)除非用于ADL,否则永远不要使用using namespace。2)除非您正在编写库组件,否则设计良好的类不应具有析构函数、复制构造函数或赋值运算符。如果有这些内容,请将有问题的逻辑因素提取到单一责任库组件中,然后查看2)。)

1
我没有点踩。但是我要指出,对于正在使用Qt的人来说,“永远不要使用指针”这样的建议似乎会让人感到困惑。每个Qt小部件示例都充满了new调用,这些调用没有与删除配对。我更喜欢addWidget采用QSharedPointer,但是... https://dev59.com/0HI-5IYBdhLWcg3w1sPi.html - HostileFork says dont trust SE
2
@HostileFork:关于Qt的问题,说得好……但我想这更多地反映了Qt而不是C++!我已经有一段时间没有使用它了,我想如果加入大量的unique_ptr,它是否可以得到改善……希望最终会有一个C++11版本的Qt来进行一些清理。另外请注意,问题及我的回答都是关于“C++中的对象”,而不是关于“Qt最佳实践”的。 - Kerrek SB
他们宣布Qt5不再“要求”C++11。有些人呼吁这样做,我也是其中之一......但他们是务实主义者。然而,如果你编译为C++0x/C++11,Qt4.8已经在底层进行了一些改进。如果在QObject的“所有权树”中传递的裸指针被替换为智能指针,比如QSharedPointer,我会很高兴,但他们不太可能在短期内对接口进行如此大的改变:http://doc.qt.nokia.com/4.7-snapshot/objecttrees.html - HostileFork says dont trust SE
2
你完全可以在Qt中使用智能指针(它们甚至有自己的QSharedPtr和QScopedPtr等,基本上模仿了Boost的指针)。但是,Qt API严重依赖于裸指针,并通过为每个对象指定“父对象”来管理所有权。 - jalf
2
无论如何,@KerrekSB的答案在C++方面肯定是正确的。Qt受到API设计者很久以前设计的影响,这些人对“现代”C++并不太熟悉。他们坚持使用更像Java而不是C++的API,并要求你解决它引起的问题。但即使你正在使用Qt,也不要犯认为这是良好的C++风格的错误。 - jalf
显示剩余3条评论

3
当您使用第一种语法时,您正在创建一个名为str、类型为QString且初始值为"my string"的对象。
在第二个声明中,您创建了一个名为strQString类型指针。
指针不保存值,它们指向存储值的内存位置,这是一个重要的区别。

2

方法一采用自动内存管理(对象在超出范围时将被删除)。

方法二是手动内存管理 - 直到调用delete str;才会被删除。 如果您忘记删除它 - 这会创建我们所谓的内存泄漏!

通常,除非您有使用指针的原因(减少错误的机会),否则方法一将是最佳选择。

您可能会发现这很有帮助: 为什么要使用指针?


2
方法一创建的QString对象在栈上分配内存,而方法二创建的QString对象在自由存储区分配内存。(注意,QString s = "hello" 和 QString s("hello") 是完全相同的。)正如parapura rajkumar所说,如果可以的话,尽可能使用方法一,否则使用方法二。
方法一有许多优点,其中最重要的是自动内存管理。当其超出作用域时,该QString占用的内存将自动释放,因此您无需做任何事情来释放其内存。方法二需要您在完成后使用delete来释放内存,否则会出现内存泄漏。
另一个优点是,在栈上创建对象比在自由存储区快得多。
必须使用方法二的情况是当您需要该对象的寿命超过您所在的作用域时。然后,您会使用new在自由存储区分配它,这样它就会持续到您调用delete为止,并且传递指针。

所有的类都可以使用方法1实例化吗? - Roman
@Am. 是的,如果它们有一个公共构造函数(实际上如果它们有一个_可访问的_构造函数;如果你是一个友元函数,那么你甚至可以访问私有构造函数)。如果它们没有可访问的构造函数,它们也无法使用方法2进行实例化。 - Seth Carnegie

1

什么时候使用方法1,什么时候使用方法2是正确的?

在这个例子中,当你可以使用指针并不明显。但是如果你创建自己的类,例如Images,它保存了大量的数据,并且你想要在函数或方法中传递该类的对象,那么我建议你使用指向对象的指针而不是对象本身。为什么?如果你传递一个指向函数的指针,你只传递了对象的内存地址(你复制了几个字节),但是如果你传递对象本身,那么你传递了大量的数据,这可能会减慢你的应用程序。

让我们看另一个例子:假设你有一个带有三个参数的函数,函数体应该在函数结束时更改每个参数。正如你所知,函数只能返回一个值,因此你可以在函数体中使用指针来更改每个参数。

使用指针有很多原因,你在使用它们时应该小心。我建议你阅读一些关于这个主题的文章。

我希望你理解了区别。关键概念是对象地址和对象本身之间的区别!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接