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

gEd*_*ger 1532 c++ pointers c++11

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

Object *myObject = new Object;
Run Code Online (Sandbox Code Playgroud)

而不是:

Object myObject;
Run Code Online (Sandbox Code Playgroud)

或者,不要使用函数,比如说testFunc():

myObject.testFunc();
Run Code Online (Sandbox Code Playgroud)

我们要写:

myObject->testFunc();
Run Code Online (Sandbox Code Playgroud)

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

Jos*_*eld 1515

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

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

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

动态分配

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

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

  1. 您需要该对象比当前作用域更长 - 该特定对象位于该特定内存位置,而不是它的副本.如果你可以复制/移动对象(大多数时候你应该),你应该更喜欢自动对象.
  2. 您需要分配大量内存,这可能很容易填满堆栈.如果我们不必关心这个(大部分时间你不应该),那将是很好的,因为它实际上超出了C++的范围,但不幸的是我们必须处理系统的现实我们正在为...开发.

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

指针

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

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

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

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

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

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

  • "你需要这个对象比目前的范围更长." - 关于此的另一个注意事项:在某些情况下,您似乎需要该对象超过当前范围,但实际上并非如此.例如,如果将对象放在向量中,对象将被复制(或移动)到向量中,并且原始对象在其范围结束时可以安全地销毁. (80认同)
  • 请记住s/copy/move /现在很多地方.返回对象绝对不意味着移动.您还应注意,通过指针访问对象与其创建方式正交. (24认同)
  • 我想念这个答案明确提到RAII.C++是(几乎所有)关于资源管理的全部内容,而RAII是在C++上实现它的方式(原始指针生成的主要问题是:打破RAII) (15认同)
  • 智能指针在C++ 11之前就已存在,例如boost :: shared_ptr和boost :: scoped_ptr.其他项目有自己的等价物.你不能得到移动语义,并且std :: auto_ptr的赋值是有缺陷的,所以C++ 11改进了一些东西,但建议仍然很好.(还有一个令人遗憾的挑剔,它还不足以访问_a_ C++ 11编译器,所有编译器都可能需要你的代码来支持C++ 11.是的,Oracle Solaris Studio,我是看着你.) (11认同)
  • @ MDMoore313您可以编写`Object myObject(param1等...)` (7认同)
  • 你指出一个人应该总是使用适当的工具来完成工作**真的很有用.在阅读这个答案之前,我一直认为应该总是使用**不合适的**工具来完成任何工作. (7认同)
  • "*只显示有多少坏C++程序员*"这个"*使用指针代替对象*"(即用C++编程,就像它是Java一样)就是我所说的***Java ++***.这是一种非常普遍的做法,这让我很伤心:( (5认同)
  • 这个答案非常以C++ 11为中心,并在第一句话中作出了荒谬的指责.我猜这几十年来写过MySQL,Webkit,Gecko和数亿行专有代码的人都是糟糕的程序员. (5认同)
  • 此外,像Qt这样的库有自己奇怪的内存管理系统.在这种情况下,必须使用原始(而不是智能)指针. (4认同)
  • @rajatkhanduja他做出了这样的区分:在他的观点1的括号中("那个特定的对象......不是它的副本").从技术上讲,这意味着"当身份重要时". (3认同)
  • @marczellm当然,如果你有权访问C++ 11编译器,你只能使用C++ 11的智能指针 - 当你这样做时,你应该*使用它们.智能指针的重要方面是它们有助于单一责任原则.他们的责任是内存管理,为动态对象提供某种所有权语义.这意味着您自己的课程不需要关心内存管理,并且可以专注于自己的职责. (3认同)
  • "非常不幸的是你经常看到动态分配.这只能说明有多少糟糕的C++程序员." 那是真的吗?某些语言(Objective-C)甚至不允许您非动态地分配对象.如果大多数用例的缺点并不那么重要,为什么不简单地以一致的方式做所有事情呢? (3认同)
  • @NicolasEdwards不,这是被复制的对象本身.也许你在想Java,哪里都是你复制的参考? (3认同)
  • 我将"与C代码接口"添加到使用指针的原因 - 这通常是通过回调来获取函数指针和void*数据指针. (2认同)
  • 如果您描述堆栈和堆以及它们之间的差异,将会很有帮助。理解这些概念有助于解释这一切背后的“原因”。 (2认同)
  • PS:我想举一个大型,成熟的C ++代码库的示例,其中->不是访问数据的主要运算符。 (2认同)
  • @jwg并不是每个人都应该考虑工具的适当性。有些工具比其他工具更合适。 (2认同)

Tem*_*Rex 168

指针有很多用例.

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

请注意,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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • *"使用new运算符创建指向资源的指针是一种反模式"*我认为你甚至可以增强它*使原始指针拥有某些东西是反模式*.不仅是创建,而且将原始指针作为参数传递或返回值意味着所有权转移IMHO因为`unique_ptr`/move语义而被弃用 (17认同)
  • 在任何地方使用智能指针都是一种反模式.有一些特殊情况适用,但大多数情况下,与动态分配(任意生命周期)争论的相同理由也反对任何常见的智能指针. (4认同)
  • @JamesKanze我并不是说暗示智能指针应该在任何地方使用,只是为了所有权,而且原始指针不应该用于所有权,而只能用于视图. (2认同)
  • @TemplateRex看起来有些愚蠢,因为'hun(b)`还需要知识签名,除非你知道你提供错误的类型直到编译.虽然引用问题通常不会在编译时被捕获并且需要花费更多精力进行调试,但如果您检查签名以确保参数正确,那么您还将能够查看是否有任何参数是引用因此参考位变为无问题(特别是在使用显示所选功能的签名的IDE或文本编辑器时).还有,`const&`. (2认同)

Ger*_*s R 128

这个问题有很多优秀的答案,包括前向声明,多态等的重要用例,但我觉得你问题的"灵魂"的一部分没有得到解答 - 即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
Run Code Online (Sandbox Code Playgroud)

与此最接近的是:

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.
Run Code Online (Sandbox Code Playgroud)

让我们看看替代的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...
Run Code Online (Sandbox Code Playgroud)

想到它的最好方法是 - 或多或少 - 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.
Run Code Online (Sandbox Code Playgroud)

也就是说,使用指针不一定是正确或错误的处理方式; 然而,其他答案也令人满意.一般的想法是,在C++中,您可以更好地控制对象的生命周期以及它们将存在的位置.

回到主点 - Object * object = new Object()构造实际上是最接近典型Java(或C#)的语义.

  • `Object2现在"死了"`:我认为你的意思是`myObject1`或更准确地说是myObject1指向的对象. (7认同)
  • 事实上,如果这是一个程序,那么就没有其他任何事情发生.值得庆幸的是,这只是一个解释片段,展示了C++中的指针如何表现 - 以及RAII对象无法替代原始指针的少数几个地方之一,正在研究和学习原始指针...... (7认同)
  • 确实!改写了一下. (2认同)
  • `Object object1 = new Object(); Object object2 = new Object();`是非常糟糕的代码.第二个新的或第二个Object构造函数可能会抛出,现在object1被泄露了.如果你使用的是原始``s,你应该尽快在RAII包装器中包装`new`ed对象. (2认同)

小智 78

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

  • @berkus:`std :: unique_ptr <T>`与`T`的前向声明一起工作.你只需要确保在调用`std :: unique_ptr <T>`的析构函数时,`T`是一个完整的类型.这通常意味着包含`std :: unique_ptr <T>`的类在头文件中声明其析构函数,并在cpp文件中实现它(即使实现为空). (13认同)
  • 这真的增加了有用信息的组合,很高兴你成为一个答案! (7认同)
  • std :: shared_ptr <T>也适用于T的前向声明(std :: unique_ptr <T>**不**) (3认同)

use*_*320 71

前言

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

我们走吧

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

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

{
    std::string s;
}
// s is destroyed here
Run Code Online (Sandbox Code Playgroud)

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

{
    std::string* s = new std::string;
}
delete s; // destructor called
Run Code Online (Sandbox Code Playgroud)

这与newC#和Java中普遍存在的语法无关.它们用于完全不同的目的.

动态分配的好处

1.您不必事先知道阵列的大小

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

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Run Code Online (Sandbox Code Playgroud)

当然,如果你使用的std::string替代,std::string内部自身的大小,这样不应该是一个问题.但基本上这个问题的解决方案是动态分配.您可以根据用户的输入分配动态内存,例如:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Run Code Online (Sandbox Code Playgroud)

旁注:许多初学者犯的一个错误就是使用可变长度数组.这是一个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
Run Code Online (Sandbox Code Playgroud)

另一方面,你可以用指针做任何你想做的事.不幸的是,因为指针和数组之间的区别是用Java和C#手动挥手的,所以初学者并不了解它们之间的区别.

3.多态性

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();
}
Run Code Online (Sandbox Code Playgroud)

因此,如果只有Triangles具有Rotate函数,那么如果您尝试在类的所有对象上调用它,则会出现编译器错误.使用dynamic_cast,您可以模拟as关键字.要清楚,如果转换失败,则返回无效指针.因此!test,本质上是检查是否test为NULL或无效指针的简写,这意味着强制转换失败.

自动变量的好处

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

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

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

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

许多编译器所做的一个优化就是称为elisionreturn value optimization.这些东西可以消除对非常大的对象有用的不必要的copys,例如包含许多元素的向量.通常,通常的做法是使用指针来转移所有权,而不是复制大对象来移动它们.这导致了移动语义智能指针的开始.

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

  • 老问题,但在代码段``{std :: string*s = new std :: string; }删除s; //析构函数调用``....当然这个``delete``不会起作用,因为编译器不会再知道``s`了什么? (12认同)
  • "Java炒作机器希望你相信" - 也许是在1997年,但现在这已经不合时宜了,2014年将Java与C++进行比较已不再有动力了. (4认同)
  • @Justin什么是指针,如果不是可空的引用? (3认同)
  • 我不给-1,但我不同意所写的开场陈述.首先,我不同意有任何"炒作" - 可能是围绕Y2K,但现在两种语言都很好理解.其次,我认为它们非常相似--C++是C的孩子,与Simula结合,Java增加了虚拟机,垃圾收集器和HEAVILY减少了功能,C#简化并重新引入了缺少Java的功能.是的,这使得模式和有效使用方式大不相同,但了解常见的基础设施/设计是有益的,这样才能看出差异. (2认同)
  • @James Matta:内存就是内存,这当然是正确的,它们都是从同一物理内存分配的,但需要考虑的一件事是,使用堆栈分配的对象获得更好的性能特征是很常见的,因为堆栈 -或者至少是其最高级别 - 当函数进入和退出时,在缓存中很有可能成为“热”,而堆则没有这样的好处,因此如果您在堆中追逐指针,您“可能”会获得多个缓存错过了你“可能”不会出现在堆栈上的内容。但所有这些“随机性”通常有利于堆栈。 (2认同)

Kir*_*kov 24

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

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

C++让您与对象的生命周期管理面对面.好吧,有办法处理它(智能指针系列,Qt中的QObject等),但它们都不能用于像GC那样的"火灾和遗忘"方式:你应该始终牢记内存处理.您不仅要关心破坏对象,还必须避免多次破坏同一个对象.

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

所以,回到你的问题.

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

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

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

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

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

例:

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
Run Code Online (Sandbox Code Playgroud)

  • 感谢std ::指出这一点,我编辑了答案 (15认同)
  • 很可靠的是,如果没有为构造函数提供包含引用变量的初始化列表,则不能将引用作为成员.(必须立即初始化引用.即使是构造函数主体也无法设置它,IIRC.) (2认同)

mar*_*inj 20

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

如果您使用以下内容,我将比较它在函数体内的工作原理:

Object myObject;
Run Code Online (Sandbox Code Playgroud)

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

如果你在函数体内写:

 Object *myObject = new Object;
Run Code Online (Sandbox Code Playgroud)

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

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

从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"); 
Run Code Online (Sandbox Code Playgroud)

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

  • 在指针的情况下,`myObject`仍将被销毁,就像任何其他局部变量一样.不同之处在于它的值是对象的*指针*,而不是对象本身,而哑指针的破坏不会影响其指针.所以*对象*将在破坏后存活下来. (6认同)

Kar*_*ram 19

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

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

  • `Object obj`并不总是在堆栈上 - 例如全局变量或成员变量. (6认同)
  • @LightnessRacesinOrbit我只提到了块中分配的对象,而不是全局变量和成员变量.事情是不清楚,现在纠正它 - 在答案中"在一个街区内"添加.希望它现在不是虚假信息:) (2认同)

in *_*elp 12

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

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


ST3*_*ST3 6

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

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

Object *myObject = new Object;
Run Code Online (Sandbox Code Playgroud)

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

delete myObject;
Run Code Online (Sandbox Code Playgroud)

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


现在一些介绍已经结束并回过头来回答问题.

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

看看,你有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); 这与第三个相同,但无法更改对象值.

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


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


Que*_*est 5

让我们说你有class A那个包含class B当你想调用class B外面的一些函数时,class A你只需要获得一个指向这个类的指针,你可以做任何你想做的事情,它也会改变class B你的上下文class A

但要小心动态对象


Roh*_*hit 5

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

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


归档时间:

查看次数:

298194 次

最近记录:

6 年 前