C + + 对象实例化

我是一个试图理解 C + + 的 C 程序员。许多教程使用以下代码片段演示了对象实例化:

Dog* sparky = new Dog();

这就意味着你以后会:

delete sparky;

这就说得通了。现在,在不需要动态内存分配的情况下,是否有任何理由使用上面的代替

Dog sparky;

一旦 Sparky 离开视野范围,就让毁灭者被召唤?

谢谢!

152636 次浏览

那么,使用指针的原因与使用用 malloc 分配的 C 中的指针的原因是完全相同的: 如果您希望您的对象比您的变量存在的时间更长的话!

如果可以避免的话,强烈建议不要使用新的操作符。特别是当您使用异常时。通常,让编译器释放对象要安全得多。

The only reason I'd worry about is that Dog is now allocated on the stack, rather than the heap. So if Dog is megabytes in size, you may have a problem,

如果您确实需要使用新建/删除路线,请注意异常。正因为如此,您应该使用 auto _ ptr 或者其中一种升级智能指针类型来管理对象生存期。

当您可以分配堆栈时(除非出于某种原因您有一个很小的堆栈并希望使用堆)没有理由新建(在堆上)。

You might want to consider using a shared_ptr (or one of its variants) from the standard library if you do want to allocate on the heap. That'll handle doing the delete for you once all references to the shared_ptr have gone out of existance.

相反,您应该总是更喜欢堆栈分配,根据经验,您的用户代码中永远不应该有 new/delete。

正如您所说的,当变量在堆栈上声明时,它的析构函数会在它超出作用域时自动调用,这是跟踪资源生命周期和避免泄漏的主要工具。

因此,一般来说,每次需要分配资源时,无论是内存(通过调用 new)、文件句柄、套接字还是其他任何东西,都要将其包装在构造函数获取资源并由析构函数释放的类中。然后您可以在堆栈上创建该类型的对象,并且可以保证在资源超出作用域时将其释放。这样,您就不必到处跟踪新建/删除对,以确保避免内存泄漏。

这个习语最常见的名字是 拉尔

还要研究智能指针类,这些类用于在极少数情况下将结果指针封装在专用 RAII 对象之外,这种情况下必须分配一些具有新内容的内容。相反,您将指针传递给智能指针,然后智能指针通过引用计数等方式跟踪其生存期,并在最后一个引用超出作用域时调用析构函数。标准库具有用于简单的基于范围的管理的 std::unique_ptr,以及用于实现共享所有权的引用计数的 std::shared_ptr

许多教程演示对象 实例化使用一个代码片段,如..。

因此,您发现大多数教程都很糟糕。 ;) 大多数教程都会教您一些糟糕的 C + + 实践,包括在不需要的时候调用 new/delete 来创建变量,以及让您难以跟踪分配的生命周期。

把堆作为一个非常重要的不动产,并非常明智地使用它。基本的经验法则是使用堆栈 只要有可能,并在没有其他办法时使用堆。通过在堆栈上分配对象,您可以获得许多好处,例如:

(1)在出现异常的情况下,您不必担心堆栈解除

(2)。您不必担心由于堆管理器分配的空间超出需要而导致的内存碎片。

我已经看到了这个反模式从人们不完全得到 & address-of 操作符。如果他们需要用指针调用函数,他们总是在堆上分配,这样他们就会得到一个指针。

void FeedTheDog(Dog* hungryDog);


Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;


Dog goodDog;
FeedTheDog(&goodDog);

我在 Visual Studio 中遇到过同样的问题,你必须使用:

yourClass->classMethod();

而不是:

你的 Class.classMethod () ;

还有一个其他人没有提到的额外原因,为什么可以选择动态创建对象。基于堆的动态对象允许您使用 polymorphism

尽管在堆栈上拥有内容在分配和自动释放方面可能是一个优势,但它也有一些缺点。

  1. 您可能不希望在堆栈上分配大型对象。

  2. 克劳斯·福尔曼! 想想这个代码:

#include <iostream>


class A {
public:
virtual void f();
virtual ~A() {}
};


class B : public A {
public:
virtual void f();
};


void A::f() {cout << "A";}
void B::f() {cout << "B";}


int main(void) {
A *a = new B();
a->f();
delete a;
return 0;
}

这将打印“ B”。现在让我们看看使用 Stack 时会发生什么:

int main(void) {
A a = B();
a.f();
return 0;
}

这将打印“ A”,对于那些熟悉 Java 或其他面向对象语言的人来说,这可能不太直观。原因是您不再拥有指向 B实例的指针。相反,将创建 B的实例并将其复制到 A类型的 a变量。

有些事情可能会突然发生,特别是当你刚接触 C + + 的时候。在 C 中,你有你的指针,就是这样。你知道如何使用它们,它们总是做同样的事情。在 C + + 中,情况并非如此。想象一下,当你在这个例子中使用一个参数作为一个方法的参数时会发生什么——事情变得更加复杂,如果 a的类型是 AA*,甚至是 A&(引用调用) ,结果会有很大的不同。许多组合是可能的,而且它们的行为都不同。