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

Object *myObject = new Object;

而不是:

Object myObject;

或者不用函数,让我们说testFunc(),如下所示:

myObject.testFunc();

我们必须写下:

myObject->testFunc();

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


当前回答

使用指针的一个原因是与C函数接口。另一个原因是节省内存;例如:不要将包含大量数据且具有处理器密集型复制构造函数的对象传递给函数,只需将指针传递给该对象即可节省内存和速度,尤其是在循环中,但在这种情况下引用会更好,除非您使用的是C样式数组。

其他回答

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

指针有很多用例。

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

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具有移动语义,可以避免将昂贵的对象复制到函数参数中并作为返回值。但是使用一个指针肯定会避免这些问题,并且允许在同一个对象上使用多个指针(而一个对象只能从一次移动)。

资源获取。在现代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创建的,直接由您创建或在资源类中创建)在超出范围时被销毁。

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

我有一个简单的Item类:

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

我做了一个向量来容纳它们。

std::vector<Item>库存;

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

std::vector<Item*>库存;

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

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

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

Object *myObject = new Object;

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

delete myObject;

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


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

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

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

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

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


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

假设你有一个包含类B的类A。当你想在类A之外调用类B的某个函数时,你只需要获得一个指向这个类的指针,你可以做任何你想做的事情,它也会改变类A中类B的上下文

但要小心动态对象