我来自一个 Java 背景,已经开始使用 c + + 中的对象。但对我发生的一件事是人们经常使用指针指向的对象,而不是对象本身,例如此声明︰

Object *myObject = new Object;

而不是︰

Object myObject;

或者,而不是使用一个函数,我们说testFunc(),如下︰

myObject.testFunc();

我们必须编写︰

myObject->testFunc();

但我无法找的出为什么应我们这样做。我将假定它有处理效率和速度,因为我们可以直接访问内存地址。我是否正确?

2014-03-03 11:54:16
问题评论:

感谢您对这种做法提出疑问,而不是只是跟在它后面。大多数情况下,指针是过度使用。

如果您看不到使用指针的理由,不要。喜欢的对象。更喜欢前原始指针之前的 shared_ptr unique_ptr 的对象。

注意︰ 在 java 中,(除基本类型) 的一切都是指针。因此,而是应该问相反︰ 为什么需要简单对象?

注意,在 Java 中,隐藏指针的语法。C + + 中进行代码中显式指针和非指针之间的区别。Java 使用指针无处不在。

在关闭时为范围太大严重吗?请的人,请注意这 Java + + 的编程方式,是非常常见,在 c + + 社区的最重要问题之一它应该被严肃处理。

回答:

这是非常不幸的您会看到如此频繁的动态分配。只是,它显示多少坏 c + + 程序员有。

在某种意义上,您有两个问题捆绑成一个。第一种是我们何时应该使用动态分配 (使用new)?第二个是我们何时应使用指针?

重要执行总部消息是您应该始终使用适当的工具作业在几乎所有情况下,还有一些更适当执行手动动态分配和/或使用原始指针比更安全。

动态分配

您的问题,在您已经演示了两种方法来创建对象。主要区别是该对象的存储持续时间。执行时Object myObject;在块内,则使用自动存储持续时间,这意味着它将在它超出范围时自动销毁创建该对象。在执行new Object()时,对象具有动态存储持续时间,这意味着其保持活动状态直到您显式delete它。只应使用在需要时动态存储期限。也就是说,始终愿意使用自动存储持续时间时,您可以创建对象.

您需要动态分配的两种主要情况︰

  1. 您需要 outlive 当前作用域的对象在该特定的内存位置的特定对象,不是它的一个副本。如果您在复制/移动的对象 (大多数情况下应该是) 没有什么问题,应首选自动对象。
  2. 您需要分配大量的内存,这可能很容易地填满堆栈。如果我们不需要关注我们自己与此 (大多数情况下不应该有),因为它实际上是 c + +,范围以外,但遗憾的是我们必须处理的我们正在开发的系统的实际情况就好。

当绝对需要动态分配时,应将它封装在智能指针或其他类型 (如标准容器中) 执行RAII智能指针提供动态分配对象的所有权语义。std::unique_ptrstd::shared_ptr,一看为例。如果适当地使用它们,则几乎完全可以避免执行您自己的内存管理 (请参见规则的零).

指针

但是,还有其他更多常规用途的原始指针动态分配,超出大多数有应首选的替代方案。作为之前,除非您真的需要指针总是更喜欢使用备选方案.

  1. 您需要引用语义有时您想要通过使用 (不管如何被分配) 的指针的对象因为您希望该函数到您正在传递其具有访问权限的特定对象 (不它的一个副本)。但是,在大多数情况下,您更愿意使用引用类型指针,因为这件事情是它们设计的。请注意这不一定是有关扩展超出当前范围,如上面的第 1 种情况中所示的对象的生存期。作为前,如果你没有什么问题传递对象的副本,不需要引用语义。

  2. 您需要多态性您只能调用函数以多态形式 (即,根据对象的动态类型) 通过指针或引用的对象。如果这是您需要的行为,则需要使用指针或引用。再次,引用应为首选。

  3. 通过允许nullptr对象被忽略时要传递要表示的对象是可选的如果它是一个参数,应首选使用默认参数或函数重载。否则,应首选使用一种封装此行为,如boost::optional (或也许很快, std::optional -编辑 std::optional投票从当前的 C + + 14 草稿 n3797)。

  4. 要将编译单元以提高编译时间中分离出来一个指针的有用属性是您只需要指向的类型的前向声明 (真正使用该对象,则需要定义)。这使您可以将编译过程,这可能会显著提高编译时部分中分离出来。Pimpl 的用法,请参阅.

  5. 您需要与 C 库接口或 C 样式库。此时,正在被迫使用原始指针。能做的最佳事情是使确保您仅让在最后一刻失去原始指针。您可以从智能指针,原始指针例如,通过使用它get成员函数。如果库为您希望释放句柄通过它执行一些分配,您可以经常总结句柄智能指针与适当释放该对象的自定义 deleter。

"您需要的对象 outlive 当前作用域。"-关于此附加注释︰ 其中好像需要 outlive 在当前作用域中的对象,但是真的没有的情况下。如果您将您的对象内一个向量,例如,对象将被复制 (或移动) 向量,和原始对象将被安全地销毁在其范围结束时。

请注意 s/拷贝/移动/在很多现在放置。返回的对象肯定并不意味着移动。此外应注意通过指针访问对象的创建方式与正交。

我错过 RAII 显式引用在此回答。C + + 是所有 (几乎是全部的) 关于资源管理和 RAII 是执行它在 c + + 的方法 (和生成的原始指针的主要问题︰ 打破 RAII)

C + + 11,例如 boost::shared_ptr 和 boost::scoped_ptr 前不存在智能指针。其他项目都有自己的等效项。无法获取移动语义和 std::auto_ptr 的分配有缺陷,因此,C + + 11 提高了的事情,但建议仍然完好。(和 sad nitpick,它不足以有权访问11 C + + 编译器,它的必要,所有编译器可能可能都希望您的代码以使用支持 C + + 11。的 Oracle Solaris Studio 中,我正在查看您。)

@MDMoore313 可以编写Object myObject(param1, etc...)

有许多用例有关指针。

多态行为对于多态类型,指针 (或引用) 用来避免切片︰

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

引用语义和避免复制对于非多态类型,指针 (或引用) 将避免复制可能很昂贵的对象

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

请注意,C + + 11 到函数的参数和返回值可以避免昂贵的对象的多个副本的移动语义。但使用指针明确避免的将允许对同一对象的多个指针 (反之,从一次只能移动一个对象)。

资源收购创建一个指向资源使用new运算符是现代 c + + 中的反模式使用一个特殊的资源类 (一种标准容器) 或智能指针std::unique_ptr<>std::shared_ptr<>)。请看︰

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

vs。

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

原始指针只应为"视图"并不以任何方式参与所有权,不论其是通过直接创建或隐式的返回值。请参阅此从 c + + 常见问题问答.

更细粒度的生命时间控件每次复制共享的指针 (例如作为函数的参数) 指向的资源是保持活动状态。当超出范围时立即被常规对象 (不创建new的由您直接或内部资源类)。

"创建一个指向资源使用 new 运算符是反模式"我认为即使无法对事情有自己的原始指针是反模式增强的。创建后,不仅能传递原始指针作为参数或返回值,这意味着所有权转让 IMHO unique_ptr注意语义以来被否决

@dyp tnx,更新,并对 c + + 常见问题问答关于此主题的引用。

无处不在使用智能指针是一种反模式。有几个特殊的情况︰ 它是适用的但大多数情况下,同样的原因,认为动态分配 (任意生命周期) 的辩驳任何常规智能指针也。

我不好意思表示,应该只是为了拥有,无处不在使用智能指针,也该原始指针不应使用的所有权,但只针对视图的 @JamesKanze。

除非您是罚款,不知道您之前编译提供错误的类型,看起来稍微愚蠢还提供hun(b) ,@TemplateRex 要求签名的知识。而引用问题通常在编译时不会钓,需要花费更多的工作要进行调试,如果正在检查以确保参数是正确的签名还会看到如果任何参数是引用,以便引用位成为一些非问题 (尤其是当使用显示的所选函数签名的 Ide 或文本编辑器)。此外, const&.

有许多很好回答这个问题,包括重要的用例的前向声明,多态性等,但我不是您的问题的"灵魂"的一部分的感觉回答-即语法的不同点的含义在 Java 和 c + +。

让我们看一下这两条语句︰

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

对此,最接近的是︰

C + +:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

让我们看看其他的 c + + 方法︰

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

把它的最好办法是,-更多或更少 — Java (隐式) 处理指向对象,而 c + + 可能处理指向对象的指针,或者这些对象本身。有这样的例外--例如,如果声明了 Java 的"基元"类型,它们是实际值,则复制并不是指针。因此,

Java:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

这就是说,使用指针不一定是正确或错误的方式来处理事情。但是其他的答案有介绍,令人满意。大致的想法不过是 c + + 中您有更多的控制和他们居住的对象的生存期上。

采用家庭点-Object * object = new Object()构造实际上是接近典型 Java (或 C# 就此而言) 的语义。

Object2 is now "dead": 我认为,您的意思是myObject1或更确切地说是the object pointed to by myObject1.

的确 !有点重新解释。

Object object1 = new Object(); Object object2 = new Object();是很糟糕的代码。新的第二或第二个对象构造函数可能会引发的和现在泄漏对象 1。如果您使用的原始news,应该换new在 RAII 包装 ASAP 的教育对象。

事实上,它是如果这是一种程序,并执行任何其他操作正在进行其周围。幸运的是,这只是解释代码段显示的在 c + + 的指针的行为方式-和 RAII 对象不能代替原始指针,在几个位置之一是研究和学习使用原始指针。.

使用指针的另一个好理由会的前向声明在足够大的项目中他们确实可以加快编译时。

这真的添加到组合中的有用信息,所以很高兴做它答案 !

引用过此工作。

< T > std::shared_ptr 也适用于前向声明的 t (std::unique_ptr < T >的。)

@berkus: std::unique_ptr<T>工作与T的前向声明。只需确保std::unique_ptr<T>的析构函数被调用时,T是一个完整类型。这通常意味着您包含std::unique_ptr<T>声明头文件中的析构函数和类实现在 cpp 文件中 (即使实现为空)。

@DavidStone 表示感谢,它工作 !

前言

Java 是一点也不像 c + +,相反对天花乱坠。Java 天花乱坠机希望您相信因为 Java 语法,如 c + + 语言相近。不能离事实。此错误信息是代码的的原因为什么 Java 程序员转到 c + + 并不了解他们的影响的情况下使用类似于 Java 的语法的一部分。

此后,我们转到

但我无法找的出为什么应我们这样做。我将假定它有处理效率和速度,因为我们可以直接访问内存地址。我是否正确?

相反,实际上。堆是要慢得多比堆栈,堆栈是非常简单,因为比较堆。自动存储变量 (亦即堆栈变量) 有其析构函数调用后超出范围。例如︰

{
    std::string s;
}
// s is destroyed here

另一方面,如果您使用动态分配的指针,其析构函数必须手动调用。delete为您调用此析构函数。

{
    std::string* s = new std::string;
}
delete s; // destructor called

这与 C# 和 Java 中流行的new语法无关。它们用于完全不同的目的。

动态分配的好处

1.您不必事先知道数组的大小

第一个问题会遇到很多 c + + 程序员是当正在接受来自用户的任意输入,您可以仅为堆栈变量分配固定的大小。您既不能更改数组的大小。例如︰

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

当然如果您使用的是std::stringstd::string内部调整自身的大小这样,就不会有问题。但实质上是此问题的解决方案是动态分配的。您可以分配动态内存根据输入的用户,例如︰

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

边注︰ 一个犯很多初学者是可变长度数组的用法。这是 GNU 扩展,并且一种 Clang 因为其反映许多 GCC 的扩展。因此不应将以下int arr[n]其上。

堆是堆栈相比要大得多,因为其中一个可以任意/分配重新分配尽可能多的内存,因为他/她的需要而堆栈的限制。

2.数组不是指针

如何这是您问一个优点?一旦您理解混乱/神话背后数组和指针,答案会非常明显。通常假定,它们是相同的但它们不是。此神话来自指针,可以 subscripted 就像数组那样的事实,因为数组衰减到指针在函数声明中的顶层。但是,一旦数组衰减为指针,指针将失去其sizeof信息。因此sizeof(pointer)将提供的字节数,指针的大小通常是在 64 位系统上的 8 个字节。

您不能将分配给数组,仅对其进行初始化。例如︰

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

另一方面,您可以随心所欲地使用指针。遗憾的是因为指针与数组之间的区别在 Java 和 C# 在手 waved,初学者不理解的差异。

3.多态性

Java 和 C# 有工具,使您能够将对象视为另一个,例如使用as关键字。因此,如果有人想要Player对象被视为一个Entity对象,做到Player player = Entity as Player;这是非常有用,如果您想要调用的函数时应仅适用于特定类型的同构容器上。以类似的方式下,可以获得的功能︰

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

所以说是否三角形有一个旋转的函数,则将是编译器错误,是否您尝试在类的所有对象上调用它。您可以使用dynamic_cast,模拟as关键字。要清楚,如果转换失败,则返回了无效的指针。因此!test是本质上是一种简写形式检查test是空值或无效的指针,这意味着强制转换失败。

自动变量的好处

之后看到可以执行动态分配的所有好处,您可能想知道为何不是任何人都不使用动态分配的所有时间吗?我已经告诉过您的一个原因,堆很慢。并且,如果您不需要所有的内存,不应滥用。以下是一些缺点顺序不分先后︰

  • 它是容易出现的错误。手动内存分配是很危险的容易出现泄漏。如果不能熟练地使用调试器或valgrind (内存泄漏工具),您可能从您脑海中拉出您的头发。值得庆幸的是 RAII 惯用语和智能指针来缓解这一点,但您必须熟悉规则三和规则五等做法。有很多的信息,应采取和初学者者不知道或者不关心将落入这个陷阱。

  • 不是有必要的。与 Java 和 C# 是惯用语,c + + 中使用new关键字无处不在您应只使用它如果需要。常用短语转,一切都看起来像是钉子如果有一把铁锤。而初学者开始使用 c + + 的指针 scared 并了解如何使用堆栈变量通过习惯,Java 和 C# 程序员开始使用指针而不知道它 !按其原义步进关闭错误的脚上。必须放弃一切您知道语法是一回事,因为学习语言另一种。

1.(N) RVO-亦即 (命名) 的返回值优化

许多编译器做的一个优化的几点称为elision返回值优化这些事情可以替代不必要的复制,这是很大,如包含许多要素矢量对象的有用。正常情况下,通常的做法是使用指针将所有权转让,而不复制大对象移动到其周围。这已经导致开始移动语义智能指针.

如果您使用的指针,(N) RVO 却没有发生。它是更有益、 更少出错 RVO (N),而不是返回或传递指针,如果担心优化利用。如果函数的调用方负责deleteing 动态分配的对象等,则会发生错误泄漏。它可能很难跟踪对象的所有权,如果周围像热马铃薯传递指针。只需使用堆栈变量,因为它更简单、 更好。

"所以 ! 测试是本质上是一种简写形式检查测试是否为空或无效的指针,这意味着强制转换失败。"我认为这个句子必须重写为清楚起见。

"Java 天花乱坠机希望您相信"-1997,但这也许是现在 anachronistic,不再有动机在 2014年比较 Java,c + +。

旧问题,但在代码段{ std::string* s = new std::string; } delete s; // destructor called..surelydelete此不可行,因为编译器不知道什么s不再是吗?

"..只指针与数组之间的区别是在 Java 中手 waved..."一点也不。留出该 Java 没有 (它具有可以为 null 的引用) 的指针、 数组是像任何其他类型。您不能通过丢弃数组语法并伪装对其第一个元素的引用的"handwave"引用。int[] nums = { 0 }; int zero = nums;将不进行编译。

@Justin 如果不可以为 null 引用指针是什么?

请输入您的翻译

Why should I use a pointer rather than the object itself?

确认取消