我为什么要使用指针而不是对象本身?

时间:2014-03-03 11:54:17

标签: c++ pointers c++11

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

Object *myObject = new Object;

而不是:

Object myObject;

或者不是使用函数,而是说testFunc(),如下所示:

myObject.testFunc();

我们必须写:

myObject->testFunc();

但我无法弄清楚我们为什么要这样做呢。我认为它与效率和速度有关,因为我们可以直接访问内存地址。我是对的吗?

22 个答案:

答案 0 :(得分:1451)

非常不幸的是你经常看到动态分配。这只是显示了有多少糟糕的C ++程序员。

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

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

动态分配

在您的问题中,您已经演示了两种创建对象的方法。主要区别在于对象的存储持续时间。在块中执行Object myObject;时,将使用自动存储持续时间创建对象,这意味着当它超出范围时将自动销毁。当您执行new Object()时,该对象具有动态存储持续时间,这意味着它将保持活动状态,直到您明确delete它为止。您应该只在需要时使用动态存储持续时间。 也就是说,总是更喜欢在时创建具有自动存储持续时间的对象。

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

  1. 您需要该对象超过当前范围 - 该特定内存位置的特定对象,而不是其副本。如果您可以复制/移动对象(大部分时间都应该如此),您应该更喜欢自动对象。
  2. 您需要分配大量内存,这可能很容易填满堆栈。如果我们不必关心这个(大多数时候你不应该),那将是很好的,因为它实际上超出了C ++的范围,但不幸的是,我们必须处理系统的现实我们正在开发。
  3. 如果绝对需要动态分配,则应将其封装在智能指针或执行RAII的其他类型(如标准容器)中。智能指针提供动态分配对象的所有权语义。例如,请查看std::unique_ptrstd::shared_ptr。如果您正确使用它们,您几乎可以完全避免执行自己的内存管理(请参阅Rule of Zero)。

    指针

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

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

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

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

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

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

答案 1 :(得分:165)

指针有很多用例。

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

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
}

原始指针应仅用作“视图”,并且不以任何方式涉及所有权,无论是通过直接创建还是隐式通过返回值。另请参阅this Q&A from the C++ FAQ

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

答案 2 :(得分:121)

这个问题有很多优秀的答案,包括前向声明,多态等的重要用例,但我觉得你问题的“灵魂”的一部分没有得到回答 - 即不同的语法在Java和C ++。

让我们来看看比较这两种语言的情况:

爪哇:

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“原始”类型,它们是复制的实际值,而不是指针。 所以,

爪哇:

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#)的语义。

答案 3 :(得分:77)

使用指针的另一个好理由是forward declarations。在一个足够大的项目中,它们可以真正加快编译时间。

答案 4 :(得分:66)

前言

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

然后我们去

  

但我无法弄清楚我们为什么要这样做呢。我会假设它   因为我们可以直接访问,所以与效率和速度有关   内存地址。我是对的吗?

相反,实际上。 The heap is much slower比堆栈,因为堆栈与堆相比非常简单。自动存储变量(也就是堆栈变量)一旦超出范围就会调用它们的析构函数。例如:

{
    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。数组不是指针

这对您有什么好处?一旦你理解了数组和指针背后的混乱/神话,答案就会变得清晰。通常认为它们是相同的,但它们不是。这个神话来自这样一个事实:指针可以像数组一样下标,并且因为数组在函数声明中衰减到顶层的指针。但是,一旦数组衰减到指针,指针就会丢失其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#手动挥手的,所以初学者不了解它们之间的区别。

  

第3。多态性

Java和C#具有允许您将对象视为另一个对象的工具,例如使用as关键字。因此,如果有人想将Entity对象视为Player对象,则可以执行Player player = Entity as Player;如果您打算在仅应用于a的同类容器上调用函数,这非常有用。具体类型。功能可以通过以下类似的方式实现:

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();
}

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

自动变量的好处

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

  • 容易出错。手动内存分配很危险,您很容易泄漏。如果您不熟练使用调试器或valgrind(内存泄漏工具),您可能会将头发拉出头部。幸运的是,RAII习语和智能指针可以缓解这一点,但你必须熟悉诸如“三规则”和“五规则”等实践。这需要很多信息,不知道或不关心的初学者会陷入这个陷阱。

  • 没有必要。与Java和C#不同,在C ++中使用new关键字是惯用的,只有在需要时才应该使用它。常见的说法是,如果你有锤子,一切看起来都像钉子。然而,以C ++开头的初学者害怕指针并学会习惯使用堆栈变量,而Java和C#程序员启动通过使用指针而不理解它!这实际上是走错了路。你必须放弃你所知道的一切,因为语法是一回事,学习语言是另一回事。

  

1。 (N)RVO - Aka,(命名)返回值优化

许多编译器进行的优化是 elision 返回值优化。这些东西可以消除对非常大的对象有用的不必要的copys,例如包含许多元素的向量。通常的做法是使用指向转移所有权的指针,而不是将大对象复制到移动它们周围。这导致了移动语义智能指针的开始。

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

答案 5 :(得分:23)

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

Java假装没有“谁和什么时候应该销毁这个?”这样的问题。答案是:垃圾收集器,伟大而可怕。然而,它无法提供100%的内存泄漏保护(是的,java can leak memory)。实际上,GC会给你一种虚假的安全感。您的SUV越大,您到疏散器的路程越长。

C ++让您与对象的生命周期管理面对面。好吧,有办法处理那个(smart pointers家庭,Qt中的QObject等等),但是没有一个可以像GC一样用“火灾和遗忘”的方式:你应该总是记住内存处理。你不仅要关心摧毁一个物体,还要避免多次摧毁同一个物体。

还不害怕吗?好的:循环引用 - 自己处理它们,人类。请记住:精确杀死每个对象一次,我们C ++运行时不喜欢那些捣乱尸体的人,只留下死去的人。

所以,回到你的问题。

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

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

当您使用引用时,您将获得假装为值的指针。它是一个指针,但您可以通过'。'来访问成员。

而且,再次吹响你的想法:当你用逗号分隔几个变量时,那么(看着手):

  • 每个人都有类型
  • 值/指针/引用修饰符是个体

示例:

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

答案 6 :(得分:19)

  

但是我无法弄清楚我们为什么要这样使用它?

如果你使用的话,我将比较它在函数体内的工作原理:

Object myObject;

在函数内部,一旦此函数返回,您的myObject将被销毁。因此,如果您不需要在函数之外使用对象,这将非常有用。该对象将放在当前的线程堆栈中。

如果你在函数体内写:

 Object *myObject = new Object;

然后,myObject指向的Object类实例在函数结束后不会被销毁,并且分配在堆上。

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

从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,它们将自动管理对象的生命周期。

答案 7 :(得分:19)

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

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

答案 8 :(得分:12)

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

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

答案 9 :(得分:6)

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

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

Object *myObject = new Object;

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

delete myObject;

这会调用析构函数并释放内存,看起来很简单,但是在大项目中可能很难检测到一个线程是否释放了内存,但为了这个目的你可以尝试references,这些会略微降低性能,但是与他们合作更容易。


现在一些介绍已经结束,然后再回过神来。

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

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

  1. void foo(std::string xml); 在这种情况下,您将所有数据从变量复制到函数堆栈,这需要一些时间,因此您的性能会很低。
  2. void foo(std::string* xml); 在这种情况下,您将传递指向对象的指针,速度与传递size_t变量相同,但是此声明容易出错,因为您可以传递NULL指针或无效指针。指针通常在C中使用,因为它没有引用。
  3. void foo(std::string& xml); 在这里你传递引用,基本上它与传递指针相同,但编译器做了一些东西,你不能传递无效的引用(实际上它可能创建带有无效引用的情况,但它是欺骗编译器)。
  4. void foo(const std::string* xml); 这与第二个相同,只是指针值无法更改。
  5. void foo(const std::string& xml); 这与第三个相同,但无法更改对象值。
  6. 我想提及的是,无论您选择哪种分配方式(使用new常规),您都可以使用这5种方式传递数据。


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

答案 10 :(得分:5)

假设您有class A包含class B当您要在class B之外调用class A的某个函数时,您只需获取指向此类的指针即可可以做任何你想做的事情,它也会改变你class B

class A的背景

但要小心动态对象

答案 11 :(得分:5)

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

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

答案 12 :(得分:3)

这已经详细讨论过,但在Java中,一切都是指针。它没有区分堆栈和堆分配(所有对象都在堆上分配),所以你没有意识到你正在使用指针。在C ++中,您可以根据内存需求将两者混合使用。性能和内存使用在C ++(duh)中更具确定性。

答案 13 :(得分:3)

Object *myObject = new Object;

执行此操作将创建对Object(在堆上)的引用,必须明确删除该对象以避免内存泄漏

Object myObject;

执行此操作将创建自动类型的对象(myObject)(在堆栈上),当对象(myObject)超出范围时,该对象将自动删除。

答案 14 :(得分:1)

指针直接引用对象的内存位置。 Java没有这样的东西。 Java具有通过哈希表引用对象位置的引用。你不能用这些引用来做Java中的指针算法。

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

答案 15 :(得分:0)

使用指针

  • 可以直接与内存对话。

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

答案 16 :(得分:0)

使用指针的一个原因是与C函数接口。另一个原因是节省内存;例如:代替传递包含大量数据的对象并且具有处理器密集型复制构造函数的函数,只需传递指向对象的指针,从而节省内存和速度,尤其是在循环中,在这种情况下,引用会更好,除非你使用的是C风格的数组。

答案 17 :(得分:0)

在内存利用率非常高的领域,指针很方便。例如,考虑使用minimax算法,其中将使用递归例程生成数千个节点,并且稍后使用它们来评估游戏中的下一个最佳移动,解除分配或重置的能力(如在智能指针中)显着减少了内存消耗。而非指针变量继续占用空间,直到它的递归调用返回一个值。

答案 18 :(得分:0)

我将包含一个重要的指针用例。当你在基类中存储一些对象时,它可能是多态的。

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

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

答案 19 :(得分:0)

C ++中对象指针的主要优势是允许同一个超类的多态数组和指针映射。例如,它允许将长尾小鹦鹉,鸡,知更鸟,鸵鸟等放入一系列的鸟类中。

此外,动态分配的对象更加灵活,可以使用HEAP内存,而本地分配的对象将使用STACK内存,除非它是静态的。在堆栈上具有大对象,尤其是在使用递归时,无疑会导致堆栈溢出。

答案 20 :(得分:-3)

“必要性是发明之母。” 我想指出的最重要的区别是我自己的编码经验的结果。 有时您需要将对象传递给函数。在这种情况下,如果你的对象属于一个非常大的类,那么将它作为一个对象传递它将复制它的状态(你可能不想要它......并且可能过大)从而导致复制对象的开销。而指针被修复4字节大小(假设为32位)。上面已经提到了其他原因......

答案 21 :(得分:-6)

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

我有一个简单的Item类:

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

我制作了一个矢量来容纳一堆它们。

std::vector<Item> inventory;

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

std::vector<Item *> inventory;

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