C++ 为什么我应该使用指针而不是对象本身?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22146094/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:57:59  来源:igfitidea点击:

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

c++c++11pointersc++-faq

提问by gEdringer

I'm coming from a Java background and have started working with objects in C++. But one thing that occurred to me is that people often use pointers to objects rather than the objects themselves, for example this declaration:

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

Object *myObject = new Object;

rather than:

而不是:

Object myObject;

Or instead of using a function, let's say testFunc(), like this:

或者不使用函数,让我们说testFunc(),像这样:

myObject.testFunc();

we have to write:

我们必须写:

myObject->testFunc();

But I can't figure out why should we do it this way. I would assume it has to do with efficiency and speed since we get direct access to the memory address. Am I right?

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

采纳答案by Joseph Mansfield

It's very unfortunate that you see dynamic allocation so often. That just shows how many bad C++ programmers there are.

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

In a sense, you have two questions bundled up into one. The first is when should we use dynamic allocation (using new)? The second is when should we use pointers?

从某种意义上说,您将两个问题合并为一个。首先是我们什么时候应该使用动态分配(使用new)?第二个是我们什么时候应该使用指针?

The important take-home message is that you should always use the appropriate tool for the job. In almost all situations, there is something more appropriate and safer than performing manual dynamic allocation and/or using raw pointers.

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

Dynamic allocation

动态分配

In your question, you've demonstrated two ways of creating an object. The main difference is the storage duration of the object. When doing Object myObject;within a block, the object is created with automatic storage duration, which means it will be destroyed automatically when it goes out of scope. When you do new Object(), the object has dynamic storage duration, which means it stays alive until you explicitly deleteit. You should only use dynamic storage duration when you need it. That is, you should alwaysprefer creating objects with automatic storage duration when you can.

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

The main two situations in which you might require dynamic allocation:

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

  1. You need the object to outlive the current scope- that specific object at that specific memory location, not a copy of it. If you're okay with copying/moving the object (most of the time you should be), you should prefer an automatic object.
  2. You need to allocate a lot of memory, which may easily fill up the stack. It would be nice if we didn't have to concern ourselves with this (most of the time you shouldn't have to), as it's really outside the purview of C++, but unfortunately, we have to deal with the reality of the systems we're developing for.
  1. 您需要该对象比当前作用域更有效——该特定内存位置的特定对象,而不是它的副本。如果您可以复制/移动对象(大多数情况下您应该这样做),那么您应该更喜欢自动对象。
  2. 您需要分配大量内存,这很容易填满堆栈。如果我们不必担心这个就好了(大多数时候你不应该这样做),因为它确实超出了 C++ 的范围,但不幸的是,我们必须处理系统的现实我们正在开发。

When you do absolutely require dynamic allocation, you should encapsulate it in a smart pointer or some other type that performs RAII(like the standard containers). Smart pointers provide ownership semantics of dynamically allocated objects. Take a look at std::unique_ptrand std::shared_ptr, for example. If you use them appropriately, you can almost entirely avoid performing your own memory management (see the Rule of Zero).

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

Pointers

指针

However, there are other more general uses for raw pointers beyond dynamic allocation, but most have alternatives that you should prefer. As before, always prefer the alternatives unless you really need pointers.

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

  1. You need reference semantics. Sometimes you want to pass an object using a pointer (regardless of how it was allocated) because you want the function to which you're passing it to have access that that specific object (not a copy of it). However, in most situations, you should prefer reference types to pointers, because this is specifically what they're designed for. Note this is not necessarily about extending the lifetime of the object beyond the current scope, as in situation 1 above. As before, if you're okay with passing a copy of the object, you don't need reference semantics.

  2. You need polymorphism. You can only call functions polymorphically (that is, according to the dynamic type of an object) through a pointer or reference to the object. If that's the behavior you need, then you need to use pointers or references. Again, references should be preferred.

  3. You want to represent that an object is optionalby allowing a nullptrto be passed when the object is being omitted. If it's an argument, you should prefer to use default arguments or function overloads. Otherwise, you should preferably use a type that encapsulates this behavior, such as std::optional(introduced in C++17 - with earlier C++ standards, use boost::optional).

  4. You want to decouple compilation units to improve compilation time. The useful property of a pointer is that you only require a forward declaration of the pointed-to type (to actually use the object, you'll need a definition). This allows you to decouple parts of your compilation process, which may significantly improve compilation time. See the Pimpl idiom.

  5. You need to interface with a C libraryor a C-style library. At this point, you're forced to use raw pointers. The best thing you can do is make sure you only let your raw pointers loose at the last possible moment. You can get a raw pointer from a smart pointer, for example, by using its getmember function. If a library performs some allocation for you which it expects you to deallocate via a handle, you can often wrap the handle up in a smart pointer with a custom deleter that will deallocate the object appropriately.

  1. 你需要参考语义。有时您想使用指针传递对象(不管它是如何分配的),因为您希望传递给它的函数可以访问该特定对象(而不是它的副本)。但是,在大多数情况下,您应该更喜欢引用类型而不是指针,因为这正是它们的设计目的。请注意,这不一定是将对象的生命周期延长到当前范围之外,如上面的情况 1。和以前一样,如果您可以传递对象的副本,则不需要引用语义。

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

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

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

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

回答by TemplateRex

There are many use cases for pointers.

指针有很多用例。

Polymorphic behavior. For polymorphic types, pointers (or references) are used to avoid slicing:

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

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

Reference semantics and avoiding copying. For non-polymorphic types, a pointer (or a reference) will avoid copying a potentially expensive 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

Note that C++11 has move semantics that can avoid many copies of expensive objects into function argument and as return values. But using a pointer will definitely avoid those and will allow multiple pointers on the same object (whereas an object can only be moved from once).

请注意,C++11 具有移动语义,可以避免将昂贵对象的许多副本放入函数参数和返回值中。但是使用指针肯定会避免这些,并且允许在同一个对象上有多个指针(而一个对象只能从一次移动)。

Resource acquisition. Creating a pointer to a resource using the newoperator is an anti-patternin modern C++. Use a special resource class (one of the Standard containers) or a smart pointer(std::unique_ptr<>or std::shared_ptr<>). Consider:

资源获取。使用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
}

A raw pointer should only be used as a "view" and not in any way involved in ownership, be it through direct creation or implicitly through return values. See also this Q&A from the C++ FAQ.

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

More fine-grained life-time controlEvery time a shared pointer is being copied (e.g. as a function argument) the resource it points to is being kept alive. Regular objects (not created by new, either directly by you or inside a resource class) are destroyed when going out of scope.

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

回答by Gerasimos R

There are many excellent answers to this question, including the important use cases of forward declarations, polymorphism etc. but I feel a part of the "soul" of your question is not answered - namely what the different syntaxes mean across Java and C++.

这个问题有很多很好的答案,包括前向声明、多态等的重要用例,但我觉得你的问题的“灵魂”的一部分没有得到回答——即 Java 和 C++ 中不同的语法意味着什么。

Let's examine the situation comparing the two languages:

让我们检查比较两种语言的情况:

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

The closest equivalent to this, is:

与此最接近的等价物是:

C++:

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.

Let's see the alternative C++ way:

让我们看看替代的 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...

The best way to think of it is that -- more or less -- Java (implicitly) handles pointers to objects, while C++ may handle either pointers to objects, or the objects themselves. There are exceptions to this -- for example, if you declare Java "primitive" types, they are actual values that are copied, and not pointers. So,

考虑它的最佳方式是——或多或少——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.

That said, using pointers is NOT necessarily either the correct or the wrong way to handle things; however other answers have covered that satisfactorily. The general idea though is that in C++ you have much more control on the lifetime of the objects, and on where they will live.

也就是说,使用指针不一定是正确或错误的处理方式;然而,其他答案已经令人满意地涵盖了这一点。但总体思路是,在 C++ 中,您可以更好地控制对象的生命周期,以及它们将居住的位置。

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

归根结底——该Object * object = new Object()构造实际上是最接近典型 Java(或 C#)语义的。

回答by Burnt Toast

Another good reason to use pointers would be for forward declarations. In a large enough project they can really speed up compile time.

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

回答by user3391320

Preface

前言

Java is nothing like C++, contrary to hype. The Java hype machine would like you to believe that because Java has C++ like syntax, that the languages are similar. Nothing can be further from the truth. This misinformation is part of the reason why Java programmers go to C++ and use Java-like syntax without understanding the implications of their code.

Java 与 C++ 完全不同,这与炒作相反。Java 炒作机器希望您相信,因为 Java 具有类似 C++ 的语法,所以这些语言是相似的。事实并非如此。这种错误信息是 Java 程序员在不了解其代码含义的情况下转而使用 C++ 并使用类 Java 语法的部分原因。

Onwards we go

我们继续前进

But I can't figure out why should we do it this way. I would assume it has to do with efficiency and speed since we get direct access to the memory address. Am I right?

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

To the contrary, actually. The heap is much slowerthan the stack, because the stack is very simple compared to the heap. Automatic storage variables (aka stack variables) have their destructors called once they go out of scope. For example:

相反,实际上。比栈很多,因为栈相对于堆来说非常简单。自动存储变量(又名堆栈变量)一旦超出范围就会调用其析构函数。例如:

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

On the other hand, if you use a pointer dynamically allocated, its destructor must be called manually. deletecalls this destructor for you.

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

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

This has nothing to do with the newsyntax prevalent in C# and Java. They are used for completely different purposes.

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

Benefits of dynamic allocation

动态分配的好处

1. You don't have to know the size of the array in advance

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

One of the first problems many C++ programmers run into is that when they are accepting arbitrary input from users, you can only allocate a fixed size for a stack variable. You cannot change the size of arrays either. For example:

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

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

Of course, if you used an std::stringinstead, std::stringinternally resizes itself so that shouldn't be a problem. But essentially the solution to this problem is dynamic allocation. You can allocate dynamic memory based on the input of the user, for example:

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

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

Side note: One mistake many beginners make is the usage of variable length arrays. This is a GNU extension and also one in Clang because they mirror many of GCC's extensions. So the following int arr[n]should not be relied on.

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

Because the heap is much bigger than the stack, one can arbitrarily allocate/reallocate as much memory as he/she needs, whereas the stack has a limitation.

因为堆比堆栈大得多,人们可以任意分配/重新分配他/她需要的内存,而堆栈有一个限制。

2. Arrays are not pointers

2. 数组不是指针

How is this a benefit you ask? The answer will become clear once you understand the confusion/myth behind arrays and pointers. It is commonly assumed that they are the same, but they are not. This myth comes from the fact that pointers can be subscripted just like arrays and because of arrays decay to pointers at the top level in a function declaration. However, once an array decays to a pointer, the pointer loses its sizeofinformation. So sizeof(pointer)will give the size of the pointer in bytes, which is usually 8 bytes on a 64-bit system.

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

You cannot assign to arrays, only initialize them. For example:

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

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

On the other hand, you can do whatever you want with pointers. Unfortunately, because the distinction between pointers and arrays are hand-waved in Java and C#, beginners don't understand the difference.

另一方面,你可以用指针做任何你想做的事情。不幸的是,因为指针和数组之间的区别是在 Java 和 C# 中手挥的,初学者不明白其中的区别。

3. Polymorphism

3. 多态性

Java and C# have facilities that allow you to treat objects as another, for example using the askeyword. So if somebody wanted to treat an Entityobject as a Playerobject, one could do Player player = Entity as Player;This is very useful if you intend to call functions on a homogeneous container that should only apply to a specific type. The functionality can be achieved in a similar fashion below:

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

So say if only Triangles had a Rotate function, it would be a compiler error if you tried to call it on all objects of the class. Using dynamic_cast, you can simulate the askeyword. To be clear, if a cast fails, it returns an invalid pointer. So !testis essentially a shorthand for checking if testis NULL or an invalid pointer, which means the cast failed.

所以说如果只有 Triangles 有一个 Rotate 函数,如果你试图在类的所有对象上调用它,那将是一个编译器错误。使用dynamic_cast,您可以模拟as关键字。需要明确的是,如果转换失败,它将返回一个无效的指针。So!test本质上是检查是否test为 NULL 或无效指针的简写,这意味着转换失败。

Benefits of automatic variables

自动变量的好处

After seeing all the great things dynamic allocation can do, you're probably wondering why wouldn't anyone NOT use dynamic allocation all the time? I already told you one reason, the heap is slow. And if you don't need all that memory, you shouldn't abuse it. So here are some disadvantages in no particular order:

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

  • It is error-prone. Manual memory allocation is dangerous and you are prone to leaks. If you are not proficient at using the debugger or valgrind(a memory leak tool), you may pull your hair out of your head. Luckily RAII idioms and smart pointers alleviate this a bit, but you must be familiar with practices such as The Rule Of Three and The Rule Of Five. It is a lot of information to take in, and beginners who either don't know or don't care will fall into this trap.

  • It is not necessary. Unlike Java and C# where it is idiomatic to use the newkeyword everywhere, in C++, you should only use it if you need to. The common phrase goes, everything looks like a nail if you have a hammer. Whereas beginners who start with C++ are scared of pointers and learn to use stack variables by habit, Java and C# programmers startby using pointers without understanding it! That is literally stepping off on the wrong foot. You must abandon everything you know because the syntax is one thing, learning the language is another.

  • 它很容易出错。手动分配内存很危险,而且很容易发生泄漏。如果您不精通使用调试器或valgrind(内存泄漏工具),则可能会将您的头发从头上拔下来。幸运的是,RAII 习语和智能指针稍微缓解了这种情况,但您必须熟悉诸如“三规则”和“五规则”之类的实践。需要吸收的信息量很大,不知道或者不关心的初学者都会掉入这个陷阱。

  • 这不是必要的。与 Java 和 C# 习惯性地在new任何地方使用关键字不同,在 C++ 中,您应该只在需要时才使用它。俗话说,如果你有锤子,一切看起来都像钉子。刚开始接触 C++ 的初学者害怕指针,习惯性地学习使用堆栈变量,而 Java 和 C# 程序员开始使用指针而不了解它!这实际上是踩错了脚。你必须放弃你所知道的一切,因为语法是一回事,学习语言是另一回事。

1. (N)RVO - Aka, (Named) Return Value Optimization

1. (N)RVO - Aka, (Named) 返回值优化

One optimization many compilers make are things called elisionand return value optimization. These things can obviate unnecessary copys which is useful for objects that are very large, such as a vector containing many elements. Normally the common practice is to use pointers to transfer ownershiprather than copying the large objects to movethem around. This has lead to the inception of move semanticsand smart pointers.

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

If you are using pointers, (N)RVO does NOToccur. It is more beneficial and less error-prone to take advantage of (N)RVO rather than returning or passing pointers if you are worried about optimization. Error leaks can happen if the caller of a function is responsible for deleteing a dynamically allocated object and such. It can be difficult to track the ownership of an object if pointers are being passed around like a hot potato. Just use stack variables because it is simpler and better.

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

回答by Kirill Gamazkov

C++ gives you three ways to pass an object: by pointer, by reference, and by value. Java limits you with the latter one (the only exception is primitive types like int, boolean etc). If you want to use C++ not just like a weird toy, then you'd better get to know the difference between these three ways.

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

Java pretends that there is no such problem as 'who and when should destroy this?'. The answer is: The Garbage Collector, Great and Awful. Nevertheless, it can't provide 100% protection against memory leaks (yes, java canleak memory). Actually, GC gives you a false sense of safety. The bigger your SUV, the longer your way to the evacuator.

Java 假装不存在“谁以及何时应该销毁它?”这样的问题。答案是:垃圾收集器,伟大而可怕。然而,它不能提供 100% 的内存泄漏保护(是的,java可以泄漏内存)。实际上,GC 会给您一种虚假的安全感。您的 SUV 越大,前往疏散器的距离就越长。

C++ leaves you face-to-face with object's lifecycle management. Well, there are means to deal with that (smart pointersfamily, QObject in Qt and so on), but none of them can be used in 'fire and forget' manner like GC: you should alwayskeep in mind memory handling. Not only should you care about destroying an object, you also have to avoid destroying the same object more than once.

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

Not scared yet? Ok: cyclic references - handle them yourself, human. And remember: kill each object precisely once, we C++ runtimes don't like those who mess with corpses, leave dead ones alone.

还不害怕?好的:循环引用 - 自己处理它们,人类。并记住:精确地杀死每个对象一次,我们 C++ 运行时不喜欢那些与尸体混在一起的人,不管死者。

So, back to your question.

那么,回到你的问题。

When you pass your object around by value, not by pointer or by reference, you copy the object (the whole object, whether it's a couple of bytes or a huge database dump - you're smart enough to care to avoid latter, aren't you?) every time you do '='. And to access the object's members, you use '.' (dot).

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

When you pass your object by pointer, you copy just a few bytes (4 on 32-bit systems, 8 on 64-bit ones), namely - the address of this object. And to show this to everyone, you use this fancy '->' operator when you access the members. Or you can use the combination of '*' and '.'.

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

When you use references, then you get the pointer that pretends to be a value. It's a pointer, but you access the members through '.'.

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

And, to blow your mind one more time: when you declare several variables separated by commas, then (watch the hands):

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

  • Type is given to everyone
  • Value/pointer/reference modifier is individual
  • 类型是给每个人的
  • 值/指针/引用修饰符是单独的

Example:

例子:

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

回答by marcinj

But I can't figure out why should we use it like this?

但我不明白为什么我们要这样使用它?

I will compare how it works inside the function body if you use:

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

Object myObject;

Inside the function, your myObjectwill get destroyed once this function returns. So this is useful if you don't need your object outside your function. This object will be put on current thread stack.

在函数内部,myObject一旦该函数返回,您的意志就会被销毁。因此,如果您不需要函数之外的对象,这将很有用。这个对象将被放在当前线程堆栈上。

If you write inside function body:

如果你在函数体内写:

 Object *myObject = new Object;

then Object class instance pointed by myObjectwill not get destroyed once the function ends, and allocation is on the heap.

那么myObject一旦函数结束,指向的对象类实例将不会被销毁,并且分配在堆上。

Now if you are Java programmer, then the second example is closer to how object allocation works under java. This line: Object *myObject = new Object;is equivalent to java: Object myObject = new Object();. The difference is that under java myObject will get garbage collected, while under c++ it will not get freed, you must somewhere explicitly call `delete myObject;' otherwise you will introduce memory leaks.

现在,如果您是 Java 程序员,那么第二个示例更接近于 Java 下对象分配的工作方式。这一行:Object *myObject = new Object;相当于 java: Object myObject = new Object();。不同之处在于在 java myObject 下会被垃圾回收,而在 c++ 下不会被释放,你必须在某处显式调用`delete myObject;' 否则你会引入内存泄漏。

Since c++11 you can use safe ways of dynamic allocations: new Object, by storing values in shared_ptr/unique_ptr.

从 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"); 

also, objects are very often stored in containers, like map-s or vector-s, they will automatically manage a lifetime of your objects.

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

回答by Karthik Kalyanasundaram

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. Whereas if you allocate memory on heap, using Object* obj = new Object(), they continue to live in heap until you call delete obj.

在 C++ 中,分配在堆栈上的对象(Object object;块内的using语句)将只存在于它们声明的范围内。当代码块完成执行时,声明的对象将被销毁。而如果您在堆上分配内存,使用Object* obj = new Object(),它们将继续存在于堆中,直到您调用delete obj.

I would create an object on heap when I like to use the object not only in the block of code which declared/allocated it.

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

回答by in need of help

Technically it is a memory allocation issue, however here are two more practical aspects of this. It has to do with two things: 1) Scope, when you define an object without a pointer you will no longer be able to access it after the code block it is defined in, whereas if you define a pointer with "new" then you can access it from anywhere you have a pointer to this memory until you call "delete" on the same pointer. 2) If you want to pass arguments to a function you want to pass a pointer or a reference in order to be more efficient. 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 (e.g. you copy a vector full of data). When you pass a pointer all you pass is one int (depending of implementation but most of them are one int).

从技术上讲,这是一个内存分配问题,但是这里有两个更实际的方面。它与两件事有关:1)范围,当您定义一个没有指针的对象时,您将无法在定义它的代码块之后再访问它,而如果您使用“new”定义一个指针,那么您可以从任何有指向此内存的指针的地方访问它,直到在同一指针上调用“删除”。2) 如果要将参数传递给函数,则需要传递指针或引用以提高效率。当您传递一个对象时,该对象将被复制,如果这是一个使用大量内存的对象,这可能会消耗 CPU(例如,您复制一个充满数据的向量)。当你传递一个指针时,你传递的只是一个整数(取决于实现,但大多数都是一个整数)。

Other than that you need to understand that "new" allocates memory on the heap that needs to be freed at some point. When you don't have to use "new" I suggest you use a regular object definition "on the stack".

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

回答by ST3

Well the main question is Why should I use a pointer rather than the object itself?And my answer, you should (almost) never use pointer instead of object, because C++ has references, it is safer then pointers and guarantees the same performance as pointers.

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

Another thing you mentioned in your question:

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

Object *myObject = new Object;

How does it work? It creates pointer of Objecttype, allocates memory to fit one object and calls default constructor, sounds good, right? But actually it isn't so good, if you dynamically allocated memory (used keyword new), you also have to free memory manually, that means in code you should have:

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

delete myObject;

This calls destructor and frees memory, looks easy, however in big projects may be difficult to detect if one thread freed memory or not, but for that purpose you can try shared pointers, these slightly decreases performance, but it is much easier to work with them.

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



And now some introduction is over and go back to question.

现在一些介绍结束了,回到问题。

You can use pointers instead of objects to get better performance while transferring data between function.

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

Take a look, you have std::string(it is also object) and it contains really much data, for example big XML, now you need to parse it, but for that you have function void foo(...)which can be declarated in different ways:

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

  1. void foo(std::string xml);In this case you will copy all data from your variable to function stack, it takes some time, so your performance will be low.
  2. void foo(std::string* xml);In this case you will pass pointer to object, same speed as passing size_tvariable, however this declaration has error prone, because you can pass NULLpointer or invalid pointer. Pointers usually used in Cbecause it doesn't have references.
  3. void foo(std::string& xml);Here you pass reference, basically it is the same as passing pointer, but compiler does some stuff and you cannot pass invalid reference (actually it is possible to create situation with invalid reference, but it is tricking compiler).
  4. void foo(const std::string* xml);Here is the same as second, just pointer value cannot be changed.
  5. void foo(const std::string& xml);Here is the same as third, but object value cannot be changed.
  1. void foo(std::string xml);在这种情况下,您会将变量中的所有数据复制到函数堆栈中,这需要一些时间,因此您的性能会很低。
  2. void foo(std::string* xml);在这种情况下,您将传递指向对象的指针,与传递size_t变量的速度相同,但是此声明容易出错,因为您可以传递NULL指针或无效指针。指针通常用于 inC因为它没有引用。
  3. void foo(std::string& xml);在这里传递引用,基本上和传递指针是一样的,但是编译器做了一些事情,你不能传递无效的引用(实际上有可能创建无效引用的情况,但它是在欺骗编译器)。
  4. void foo(const std::string* xml);这里和第二个一样,只是指针值不能改变。
  5. void foo(const std::string& xml);这里与第三个相同,但对象值不能更改。

What more I want to mention, you can use these 5 ways to pass data no matter which allocation way you have chosen (with newor regular).

我还要说的是,无论您选择哪种分配方式(使用new常规),都可以使用这 5 种方式传递数据。



Another thing to mention, when you create object in regularway, you allocate memory in stack, but while you create it with newyou allocate heap. It is much faster to allocate stack, but it is kind a small for really big arrays of data, so if you need big object you should use heap, because you may get stack overflow, but usually this issue is solved using STL containersand remember std::stringis also container, some guys forgot it :)

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