繁体   English   中英

为什么我应该使用指针而不是 object 本身?

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

我来自 Java 背景并开始使用 C++ 中的对象。 但是我想到的一件事是人们经常使用指向对象的指针而不是对象本身,例如这个声明:

 Object *myObject = new Object;

而不是:

 Object myObject;

或者不使用 function,假设testFunc() ,如下所示:

 myObject.testFunc();

我们必须写:

 myObject->testFunc();

但我不明白我们为什么要这样做。 我认为这与效率和速度有关,因为我们可以直接访问 memory 地址。 我对吗?

很遗憾,您经常看到动态分配。 这只是表明有多少糟糕的 C++ 程序员。

从某种意义上说,你有两个问题捆绑在一起。 首先是我们什么时候应该使用动态分配(使用new )? 第二个是我们什么时候应该使用指针?

重要的带回家的信息是,您应该始终使用适当的工具来完成工作 在几乎所有情况下,都有比执行手动动态分配和/或使用原始指针更合适和更安全的方法。

动态分配

在您的问题中,您演示了两种创建 object 的方法。 主要区别在于 object 的存储时间。 当做Object myObject; 在一个块内,object 是自动创建的,存储期限是自动的,这意味着它会在超出 scope 时自动销毁。 当您执行new Object()时,object 具有动态存储持续时间,这意味着它会保持活动状态,直到您明确delete它。 您应该只在需要时使用动态存储持续时间。 也就是说,您应该始终更喜欢在可能的情况下创建具有自动存储持续时间的对象

您可能需要动态分配的主要两种情况:

  1. 您需要 object 比当前的 scope 寿命更长- 特定 object 位于特定 ZCD69B4957F06CD818D7BF3D619 位置,而不是它的副本。 如果您可以复制/移动 object(大多数时候应该这样做),您应该更喜欢自动 object。
  2. 您需要分配很多 memory ,这可能很容易填满堆栈。 如果我们不必关心这个就好了(大多数时候你不应该关心),因为它确实超出了 C++ 的范围,但不幸的是,我们必须处理系统的现实我们正在开发。

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

指针

但是,除了动态分配之外,原始指针还有其他更一般的用途,但大多数都有您应该喜欢的替代方案。 和以前一样,除非你真的需要指针,否则总是更喜欢替代方案

  1. 您需要参考语义 有时您想使用指针传递 object(不管它是如何分配的),因为您想要传递它的 function 以访问该特定 ZA8CFDE6331BD59EB2AC96F8911C4B666 (不是它的副本) 但是,在大多数情况下,您应该更喜欢引用类型而不是指针,因为这正是它们的设计目的。 请注意,这不一定是为了将 object 的使用寿命延长到当前的 scope 之上,如上述情况 1。 和以前一样,如果您可以传递 object 的副本,则不需要引用语义。

  2. 你需要多态性 您只能通过指向 object 的指针或引用以多态方式(即根据对象的动态类型)调用函数。 如果这是您需要的行为,那么您需要使用指针或引用。 同样,参考文献应该是首选。

  3. 您想通过在省略 object 时允许传递nullptr表示 object 是可选的。 如果它是一个参数,您应该更喜欢使用默认的 arguments 或 function 重载。 否则,您最好使用封装此行为的类型,例如std::optional (在 C++17 中引入 - 使用早期的 C++ 标准,使用boost::optional )。

  4. 您希望解耦编译单元以缩短编译时间 指针的有用属性是您只需要指向类型的前向声明(要实际使用 object,您需要一个定义)。 这允许您解耦编译过程的各个部分,这可能会显着缩短编译时间。 请参阅Pimpl 成语

  5. 您需要与 C 库或 C 样式库进行交互。 此时,您被迫使用原始指针。 你能做的最好的事情是确保你只在最后一刻松开你的原始指针。 您可以从智能指针获取原始指针,例如,通过使用其get成员 function。 如果一个库为您执行一些分配,它希望您通过句柄解除分配,您通常可以使用自定义删除器将句柄包装在智能指针中,该删除器将适当地解除分配 object。

指针有很多用例。

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

 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

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

 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 具有移动语义,可以避免昂贵对象的许多副本进入 function 参数并作为返回值。 但是使用指针肯定会避免这些,并允许在同一个 object 上使用多个指针(而 object 只能从一次移动)。

资源获取 使用new运算符创建指向资源的指针是现代 C++ 中的反模式 使用特殊资源 class (标准容器之一)或智能指针std::unique_ptr<>std::shared_ptr<> )。 考虑:

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

对比

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

原始指针仅应用作“视图”,而不应以任何方式涉及所有权,无论是通过直接创建还是通过返回值隐含。 另请参阅C++ 常见问题解答中的此问答

更细粒度的生命周期控制每次复制共享指针(例如,作为 function 参数)时,它指向的资源都会保持活动状态。 当离开 scope 时,常规对象(不是由new创建的,直接由您创建或在资源类中创建)被销毁。

这个问题有很多很好的答案,包括前向声明、多态等的重要用例,但我觉得你问题的“灵魂”的一部分没有得到回答——即 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", ie 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++ 中,您可以更好地控制对象的生命周期以及它们的生存位置。

Take home point -- the Object * object = new Object() construct is actually what is closest to typical Java (or C# for that matter) semantics.

前言

Java 与 C++ 完全不同,与炒作相反。 Java 炒作机器希望您相信,因为 Java 具有类似 C++ 的语法,因此语言是相似的。 没有什么比事实更离谱了。 这种错误信息是 Java 程序员 go 到 C++ 并在不了解其代码含义的情况下使用类似 Java 的语法的部分原因。

往后我们 go

但我不明白我们为什么要这样做。 我认为这与效率和速度有关,因为我们可以直接访问 memory 地址。 我对吗?

相反,实际上。 堆比栈慢很多,因为栈和堆相比非常简单。 自动存储变量(又名堆栈变量)的析构函数在 go 超出 scope 时被调用。 例如:

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

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

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

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

动态分配的好处

1.不必提前知道数组的大小

许多 C++ 程序员遇到的第一个问题是,当他们接受来自用户的任意输入时,您只能为堆栈变量分配固定大小。 您也不能更改 arrays 的大小。 例如:

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

当然,如果您使用std::string代替,则std::string内部调整自身大小,因此这应该不是问题。 但本质上解决这个问题的方法是动态分配。 您可以根据用户的输入动态分配memory,例如:

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

旁注:许多初学者犯的一个错误是使用可变长度 arrays。 这是一个 GNU 扩展,也是 Clang 中的一个,因为它们反映了 GCC 的许多扩展。 因此,不应依赖以下int arr[n]

因为堆比栈大得多,所以可以根据需要任意分配/重新分配 memory,而栈有限制。

2. Arrays 不是指针

你问这有什么好处? 一旦您了解 arrays 和指针背后的困惑/神话,答案就会变得清晰。 通常认为它们是相同的,但它们不是。 这个神话来自这样一个事实,即指针可以像 arrays 一样下标,并且因为 arrays 衰减到 function 声明中的顶层指针。 但是,一旦数组衰减为指针,指针就会丢失其sizeof信息。 所以sizeof(pointer)将以字节为单位给出指针的大小,在 64 位系统上通常为 8 个字节。

您不能分配给 arrays,只能初始化它们。 例如:

 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

另一方面,你可以用指针做任何你想做的事情。 不幸的是,由于指针和arrays的区别在Java和C#中手打,初学者不明白其中的区别。

3. 多态性

Java 和 C# 具有允许您将对象视为另一个对象的工具,例如使用as关键字。 因此,如果有人想将Entity object 视为Player object,则可以将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() }

所以说如果只有三角形有一个旋转 function,如果你试图在 class 的所有对象上调用它,那将是一个编译器错误。 使用dynamic_cast ,您可以模拟as关键字。 需要明确的是,如果强制转换失败,它会返回一个无效的指针。 所以test本质上是检查test是 NULL 还是无效指针的简写,这意味着强制转换失败。

自动变量的好处

在看到动态分配可以做的所有伟大事情之后,您可能想知道为什么没有人不一直使用动态分配? 我已经告诉过你一个原因,堆很慢。 如果你不需要所有的 memory,你不应该滥用它。 所以这里有一些不按特定顺序排列的缺点:

  • 它容易出错。 手动 memory 分配是危险的,你很容易泄漏。 如果你不熟练使用调试器或valgrind (一个 memory 泄漏工具),你可能会把头发从头上扯下来。 幸运的是,RAII 习语和智能指针稍微缓解了这一点,但您必须熟悉诸如三规则和五规则之类的实践。 需要吸收的信息很多,不知道或不在意的初学者会掉入这个陷阱。

  • 没有必要。 new和 C# 不同,在 C++ 中,您应该只在需要时使用它。 俗话说,如果你有一把锤子,一切看起来都像钉子。 而从C++入手的初学者怕指针,习惯性地学习使用栈变量,Java和C#程序员从不了解指针就开始使用。 这实际上是走错了路,你必须放弃你所知道的一切,因为语法是一回事。 学习语言是另一回事

1. (N)RVO - 又名,(命名)返回值优化

许多编译器进行的一项优化称为省略返回值优化 这些东西可以避免不必要的副本,这对于非常大的对象很有用,例如包含许多元素的向量。 通常,通常的做法是使用指针来转移所有权,而不是复制大对象来移动它们。 这导致了移动语义智能指针的出现。

如果您使用的是指针,则 (N)RVO不会发生。 如果您担心优化,利用 (N)RVO 而不是返回或传递指针更有益且不易出错。 如果 function 的调用者负责delete动态分配的 object 等,则可能发生错误泄漏。 如果指针像烫手山芋一样被传递,则很难跟踪 object 的所有权。 只需使用堆栈变量,因为它更简单更好。

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

In C++, objects allocated on the stack (using Object object; statement within a block) will only live within the scope they are declared in. When the block of code finishes execution, the object declared are destroyed. 而如果您在堆上分配 memory ,使用Object* obj = new Object() ,它们将继续存在于堆中,直到您调用delete obj

当我喜欢在声明/分配它的代码块中使用 object 时,我会在堆上创建一个 object。

C++ 为您提供了三种传递 object 的方法:通过指针、通过引用和通过值。 Java 限制您使用后一种(唯一的例外是原始类型,如 int、boolean 等)。 如果您想使用 C++ 而不仅仅是一个奇怪的玩具,那么您最好了解这三种方式之间的区别。

Java 假装不存在“谁应该在什么时候销毁这个?”这样的问题。 答案是:垃圾收集器,伟大而可怕。 尽管如此,它不能提供 100% 的保护来防止 memory 泄漏(是的, java可以泄漏 memory )。 实际上,GC 给你一种错误的安全感。 您的 SUV 越大,通往疏散器的路越长。

C++ 让您与对象的生命周期管理面对面。 好吧,有办法解决这个问题(智能指针系列,Qt 中的 QObject 等等),但是它们都不能像 GC 那样以“即发即弃”的方式使用:你应该始终牢记 memory 处理。 您不仅应该关心破坏 object,还必须避免多次破坏同一个 object。

还不害怕? 好的:循环引用 - 自己处理它们,人类。 请记住:杀死每个 object 精确一次,我们 C++ 运行时不喜欢那些乱搞尸体的人,让死人不管。

所以,回到你的问题。

当您通过值而不是指针或引用传递 object 时,您复制 object(整个 object,无论是几个字节还是一个巨大的数据库转储,都足够聪明,可以避免你呢?)每次你做'='。 要访问对象的成员,请使用“。” (点)。

当您通过指针传递 object 时,您只复制了几个字节(在 32 位系统上为 4 个,在 64 位系统上为 8 个),即 - 此 object 的地址。 为了向所有人展示这一点,您在访问成员时使用了这个花哨的 '->' 运算符。 或者您可以使用“*”和“.”的组合。

当你使用引用时,你会得到一个伪装成值的指针。 它是一个指针,但您可以通过“.”访问成员。

而且,再一次让你大吃一惊:当你声明几个用逗号分隔的变量时,然后(注意手):

  • 类型是给每个人的
  • 值/指针/引用修饰符是单独的

例子:

 struct MyStruct { int* someIntPointer, someInt; //here comes the surprise MyStruct *somePointer; MyStruct &someReference; }; MyStruct s1; //we allocated an object on stack, not in heap s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual s1.someIntPointer = &s1.someInt; *s1.someIntPointer = 2; //now s1.someInt has value '2' s1.somePointer = &s1; s1.someReference = s1; //note there is no '&' operator: reference tries to look like value s1.somePointer->someInt = 3; //now s1.someInt has value '3' *(s1.somePointer).someInt = 3; //same as above line *s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4' s1.someReference.someInt = 5; //now s1.someInt has value '5' //although someReference is not value, it's members are accessed through '.' MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back. //OK, assume we have '=' defined in MyStruct s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

但我想不通我们为什么要这样使用它?

如果您使用,我将比较它在 function 体内的工作方式:

 Object myObject;

在 function 内部,一旦 function 返回,您的myObject将被销毁。 因此,如果您在 function 之外不需要 object,这将很有用。 这个 object 将被放入当前线程堆栈。

如果你在 function 正文中写:

 Object *myObject = new Object;

那么一旦 function 结束并且分配在堆上, myObject指向的 Object class 实例将不会被销毁。

现在,如果您是 Java 程序员,那么第二个示例更接近 object 分配在 java 下的工作方式。 此行: Object *myObject = new Object; 相当于 java: Object myObject = new Object(); . 不同之处在于在 java 下 myObject 将被垃圾收集,而在 c++ 下它不会被释放,你必须在某个地方显式调用“删除 myObject;” 否则您将引入 memory 泄漏。

由于 c++11 您可以使用安全的动态分配方式: new Object ,通过将值存储在 shared_ptr/unique_ptr 中。

 std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared"); // since c++14 std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared");

此外,对象通常存储在容器中,例如 map-s 或 vector-s,它们会自动管理对象的生命周期。

从技术上讲,这是一个 memory 分配问题,但这里有两个更实际的方面。 它与两件事有关:1)Scope,当您定义没有指针的 object 时,您将无法在定义它的代码块之后访问它,而如果您使用“new”定义指针,那么您可以从任何你有指向这个 memory 的指针的地方访问它,直到你在同一个指针上调用“删除”。 2) 如果您想将 arguments 传递给 function 您想传递指针或引用以提高效率。 When you pass an Object then the object is copied, if this is an object that uses a lot of memory this might be CPU consuming (eg you copy a vector full of data). 当你传递一个指针时,你传递的只是一个 int(取决于实现,但大多数都是一个 int)。

除此之外,您需要了解“新”在需要在某个时候释放的堆上分配 memory。 当您不必使用“新”时,我建议您使用“堆栈上”的常规 object 定义。

那么主要问题是为什么我应该使用指针而不是 object 本身? 我的回答是,你应该(几乎)永远不要使用指针而不是 object,因为 C++ 有引用,它比指针更安全,并保证与指针相同的性能。

您在问题中提到的另一件事:

 Object *myObject = new Object;

它是如何工作的? 它创建Object类型的指针,分配 memory 以适应一个 object 并调用默认构造函数,听起来不错,对吧? 但实际上它并不是那么好,如果你动态分配 memory (使用关键字new ),你还必须手动释放 memory ,这意味着在代码中你应该有:

 delete myObject;

这会调用析构函数并释放 memory,看起来很容易,但是在大型项目中可能很难检测一个线程是否释放了 memory,但为此您可以尝试共享指针,这些会稍微降低性能,但使用起来要容易得多他们。


现在一些介绍已经结束,go 回到问题。

在 function 之间传输数据时,您可以使用指针而不是对象来获得更好的性能。

看一看,你有std::string (它也是对象),它包含很多数据,例如大 XML,现在你需要解析它,但为此你有 ZC1C425268E68385D1AB5074C17A void foo(...)以不同的方式声明:

  1. void foo(std::string xml); 在这种情况下,您会将变量中的所有数据复制到 function 堆栈中,这需要一些时间,因此您的性能会很低。
  2. void foo(std::string* xml); 在这种情况下,您将传递指向 object 的指针,与传递size_t变量的速度相同,但是此声明容易出错,因为您可以传递NULL指针或无效指针。 通常在C中使用指针,因为它没有引用。
  3. void foo(std::string& xml); 这里传递引用,基本上和传递指针是一样的,但是编译器做了一些事情,你不能传递无效的引用(实际上可以创建无效引用的情况,但它欺骗了编译器)。
  4. void foo(const std::string* xml); 这里和第二个一样,只是指针值不能改变。
  5. void foo(const std::string& xml); 这里和第三个一样,但是 object 的值不能改变。

我还想提一下,无论您选择哪种分配方式(使用newregular ),您都可以使用这 5 种方式来传递数据。


另一件事要提一下,当您以常规方式创建 object 时,您在堆栈中分配 memory,但是当您使用new创建它时,您分配堆。 分配堆栈要快得多,但是对于非常大的 arrays 数据来说它有点小,所以如果你需要大的 object 你应该使用堆,因为你可能会出现堆栈溢出,但通常这个问题可以使用Z2523E0C272CB676C66878ZF 容器解决std::string也是容器,有些人忘记了:)

Let's say that you have class A that contain class B When you want to call some function of class B outside class A you will simply obtain a pointer to this class and you can do whatever you want and it will also change context of class B in你的class A

但要小心动态 object

使用指向 object 的指针有很多好处 -

  1. 效率(正如您已经指出的那样)。 将对象传递给函数意味着创建 object 的新副本。
  2. 使用来自第三方库的对象。 如果您的 object 属于第三方代码,并且作者打算仅通过指针(没有复制构造函数等)使用他们的对象,那么您可以绕过此 object 的唯一方法是使用指针。 按值传递可能会导致问题。 (深拷贝/浅拷贝问题)。
  3. 如果 object 拥有资源,并且您希望所有权不应与其他对象共享。

这已经详细讨论过了,但是在 Java 中,一切都是指针。 它没有区分堆栈和堆分配(所有对象都在堆上分配),因此您没有意识到您正在使用指针。 在 C++ 中,您可以将两者混合使用,具体取决于您的 memory 要求。 性能和 memory 的使用在 C++ 中更具确定性(duh)。

 Object *myObject = new Object;

这样做将创建对 Object (在堆上)的引用,必须明确删除该引用以避免memory 泄漏

 Object myObject;

这样做将创建一个自动类型的对象(myObject)(在堆栈上),当对象(myObject)超出 scope 时,该对象(myObject)将被自动删除。

指针直接引用 object 的 memory 位置。 Java 没有这样的东西。 Java 具有引用 object 到 hash 表的位置的引用。 您不能使用这些参考在 Java 中执行任何类似指针运算的操作。

要回答您的问题,这只是您的偏好。 我更喜欢使用类似 Java 的语法。

object 指针在 C++ 中的关键优势是允许多态 arrays 和相同超类的指针映射。 例如,它允许将鹦鹉、鸡、知更鸟、鸵鸟等放入 Bird 数组中。

Additionally, dynamically allocated objects are more flexible, and can use HEAP memory whereas a locally allocated object will use the STACK memory unless it is static. 堆栈上有大对象,尤其是在使用递归时,无疑会导致堆栈溢出。

使用指针的一个原因是与 C 函数接口。 另一个原因是保存memory; for example: instead of passing an object which contains a lot of data and has a processor-intensive copy-constructor to a function, just pass a pointer to the object, saving memory and speed especially if you're in a loop, however a在这种情况下,reference 会更好,除非您使用的是 C 样式的数组。

在 memory 利用率非常高的区域,指针会派上用场。 例如,考虑一个 minimax 算法,其中将使用递归例程生成数千个节点,然后使用它们来评估游戏中的下一个最佳移动,释放或重置的能力(如智能指针)显着降低了 memory 消耗。 而非指针变量继续占用空间,直到它的递归调用返回一个值。

我将介绍一个重要的指针用例。 当您在基础 class 中存储一些 object 时,它可能是多态的。

 Class Base1 { }; Class Derived1: public Base1 { }; Class Base2 { Base *bObj; virtual void createMemerObects() = 0; }; Class Derived2 { virtual void createMemerObects() { bObj = new Derived1(); } };

所以在这种情况下,你不能将 bObj 声明为直接的 object,你必须有指针。

tl; dr:不要“使用指针而不是 object 本身”(通常)

你问为什么你应该更喜欢指针而不是 object 本身。 嗯,你不应该,作为一般规则。

现在,这条规则确实有多个例外,其他答案已经说明了它们。 问题是,这些天来,这些例外中的许多不再有效让我们考虑在接受的答案中列出的例外:

  1. 您需要参考语义。

如果您需要引用语义,请使用引用,而不是指针; 请参阅@ST3 的答案答案 事实上,有人可能会争辩说,在 Java 中,您传递的通常是引用。

  1. 你需要多态性。

如果您知道您将使用的类集,通常您可以使用std::variant<ClassA, ClassB, ClassC> (参见此处的描述)并使用访问者模式对它们进行操作。 现在,诚然,C++ 的变体实现并不是最漂亮的。 但我通常更喜欢它而不是用指针弄脏。

您想表示 object 是可选的

绝对不要为此使用指针。 您有std::optional ,并且与std::variant ,它非常方便。 改用那个。 nullopt是一个空(或“null”)可选。 而且 - 它不是一个指针。

您希望解耦编译单元以缩短编译时间。

您也可以使用引用而不是指针来实现这一点。 要在一段代码中使用Object& ,只需说class Object; ,即使用前向声明

您需要与 C 库或 C 样式库进行交互。

是的,好吧,如果您使用已经使用指针的代码,那么-您必须自己使用指针,无法解决:-(并且 C 没有引用。


此外,有些人可能会告诉您使用指针来避免复制对象。 好吧,由于返回值和命名返回值优化(RVO 和 NRVO) ,这对于返回值来说并不是真正的问题。 在其他情况下 - 引用避免复制就好了。

不过,底线规则仍然与公认的答案相同:仅当您有充分的理由需要指针时才使用指针。


PS - 如果你确实需要一个指针,你仍然应该避免使用newdelete直接 智能指针可能会更好地为您服务 - 它会自动释放(不像 Java 中那样,但仍然如此)。

用指针

  • 可直接与 memory 通话。

  • 可以通过操作指针来防止程序的大量 memory 泄漏。

"Necessity is the mother of invention." The most of important difference that I would like to point out is the outcome of my own experience of coding. Sometimes you need to pass objects to functions. In that case, if your object is of a very big class then passing it as an object will copy its state (which you might not want ..AND CAN BE BIG OVERHEAD) thus resulting in an overhead of copying object .while pointer is fixed 4-byte size (assuming 32 bit). Other reasons are already mentioned above...

已经有很多很好的答案,但让我举一个例子:

我有一个简单的项目 class:

 class Item { public: std::string name; int weight; int price; };

我制作了一个向量来容纳一堆。

std::vector<Item> inventory;

我创建了一百万个 Item 对象,并将它们推回向量上。 我按名称对向量进行排序,然后对特定项目名称进行简单的迭代二进制搜索。 我对程序进行了测试,完成执行需要 8 多分钟。 然后我像这样更改我的库存向量:

std::vector<Item *> inventory;

...并通过 new 创建我的百万个 Item 对象。 我对代码所做的唯一更改是使用指向项目的指针,除了最后为 memory 清理添加的循环。 该程序在 40 秒内运行,或者比 10 倍的速度提升要好。 编辑:代码位于http://pastebin.com/DK24SPeW通过编译器优化,它在我刚刚测试过的机器上只显示了 3.4 倍的增长,这仍然相当可观。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM