C++实例 VS 指针 VS 引用

4
我有一个程序,它会创建许多我自己定义的Class的实例。然后我会对它们进行操作:读取、更改、将它们传递给一些第三方方法等。
这里有三个问题:
1. 我应该存储实例的向量还是指针的向量?为什么?
2. 如果我不想改变一个实例,我应该传递一个实例还是它的指针给函数呢?为什么?
3. 如果我想改变一个实例,我应该传递一个指针还是引用?为什么?
感谢您的帮助!
示例代码:
class Player {
    public:
        static const char               width = 35;
        static const char               height = 5;
        static const char               speed = 15;

        int                             socketFD;
        float                           xMin;
        float                           xMax;

        char                            status;
        float                           x;
        float                           y;
        char                            direction;

                                        Player ( int );
        void                            Reset();
        void                            Move();
        void                            SetHost();
        void                            SetClient();

    private:
        void                            EscapeCheck();
};
6个回答

6
我应该存储实例数组还是指针数组?为什么?
如果“Class”是基类,并且你想防止对象切片,则存储指针。否则,存储对象。
如果我不想改变一个实例,我将向函数传递实例还是指向它的指针?为什么?
如果你按值传递,你不会修改原始对象,因为你在使用副本。如果你传递指针或引用,你将其标记为const:
void foo ( MyClass const* );
void foo ( MyClass const& );

如果我想更改一个实例,我应该传递指针还是引用?为什么?
两种方式都可以。个人偏好使用引用,因为指针与动态分配更紧密相关。
现在,RAII经常被使用,因此使用智能指针作为优秀的替代方案。

如果Class是一个基类,并且您想要防止对象切片,那么您需要存储指针。如果Class是由我设计的,例如包含两个字符串和两个整数,该怎么办? - Kolyunya
@Kolyunya 你将它用作基类吗?它是多态的吗? - Luchian Grigore
我现在不太清楚什么是“基类”。它只是一个带有许多常量、变量和方法的类。假设它不是多态的。如果它多态的,那会有什么变化呢? - Kolyunya
例如,如果你有一个从类A派生出来的类B和一个A对象的向量,如果你在向量中插入B对象,它们将被切片。多态将不再起作用。 - Luchian Grigore
@Kolyunya 不需要。你只需使用players.push_back(Player(socketFD)); - Luchian Grigore
显示剩余2条评论

4
这取决于使用情况。根据上下文,任何一种解决方案都可以。您可能希望存储多态类型的基类,或者您可能希望避免复制元素(例如,处理不可复制的类型时)。在这些情况下,应存储指针或智能指针
然后您应该传递一个const引用(对const实例的引用),以避免更改实例。
void foo(const Foo& f);

如果我想要更改一个实例,我应该传递指针还是引用?为什么?
然后,您应该传递非const引用(对非const实例的引用)。这假设您真的想更改现有实例:
void foo(Foo& f);

但是什么时候才会使用指针呢?目前我能想到的唯一情况是,当传递的实体可能具有无效或空值时。使用引用是不可能的,或者至少不容易实现。指针可以被设置为0NULL(相同)或C++11中的nullptr

void (Foo* f) {
  if (f) {
    // do something with pointee
  } else {
    // deal with it
  }
}

3

对于第二个问题,这有点取决于情况。最有效的方法可能是传递一个常量引用。

通常,尽可能避免使用指针。如果你必须使用指针,请使用智能指针


3

我应该存储实例数组还是指针数组?为什么?

如果您的对象很大(即大于2-3个基本类型),则应存储指向动态分配实例的指针数组。这将使重新排序对象和调整/重新分配容器更快。如果您正在使用多态性,您几乎必须使用指针(请注意,引用也是多态性)指向基类。这将防止切片,因为数组的所有元素必须具有相同的大小,因此如果要存储多个对象,则指向基类的指针是正确的方法。

您可能更喜欢将实例存储在容器中的情况是,如果您只有一个类型(没有继承和多态性)并且它不是那么大(即有几个基本成员类型)。对于更大的对象,您可能希望使用链表而不是数组,以避免昂贵的插入和重新分配。

如果我不想更改实例,我传递给函数实例还是指向它的指针?为什么?

传递常量对象的指针或常量引用。除非您传递一个基本类型,在这种情况下,您可以简单地传递一个便宜的副本,并避免修改原始对象。 是传递常量对象的指针还是常量引用,取决于您的使用场景,引用更容易使用,就像普通实例一样,但不能像指针一样更改为引用其他对象。

如果我确实想更改实例,我传递指针还是引用?为什么?

如果您打算更改实例,则传递指针或引用。两者中的哪一个-见上文。这样可以直接修改特定的实例。您也可以按值传递,但它将创建一个临时副本,您稍后需要将其重新分配给原始对象。这没有意义,如果修改是您的意图,请始终选择指针或引用。即使您不打算修改对象,传递常量指针或引用仍然要便宜得多,因为它们的大小与int相同,而对象可能会显着变大,因此没有复制它的必要性。如果您传递基本类型,可以通过传值来实现,尽管如果您打算修改对象,则仍需要进行一次额外的分配,这是可以避免的。


谢谢您的回复。我的类中没有多态性,但它包含许多变量。您说,我应该存储指针以更快地访问对象,对吗?我已经更新了问题... - Kolyunya
@Kolyunya - 如果您向向量添加更多对象,它最终会耗尽容量并重新分配自身,如果您有许多对象,则重新分配可能需要很长时间,您可以通过使用指针而不是实例来将其最小化。您必须根据您的使用场景进行测试。看起来像是游戏对象,您可能可以通过存储实例来解决问题,我怀疑您不会存储数百万个对象。 - dtech
提前感谢你!那么,你建议我使用一个法则:“如果可以使用对象-就应该使用对象”,对吗?这意味着“只有在没有其他选择的情况下才使用指针”,我的理解正确吗? - Kolyunya
@Kolyunya - 嗯,是的,但在指针和实例之间选择并不是唯一的选项,对于更大的对象,您可以选择使用列表或双端队列,它们允许非常快速的插入/重新排序,并且不需要重新分配内存,但也有一些其他缺点。 - dtech
@Kolyunya - 好的,在这种情况下,不要使用 new 关键字,你不需要它,只需创建一个本地的 Player 并将其 push_back 到数组中即可 - 它将被复制到数组中。创建指针、动态分配对象并解引用指针以获取对象引用是没有意义的。没有 delete,你创建的 new Player() 不会被删除,会导致内存泄漏,每次运行该方法都会积累。当超出方法范围时,本地/自动播放器将自动删除。 - dtech
显示剩余9条评论

1
  1. 如果数组适合您的需求,那么实例数组更好,因为您可以节省指针本身以及分配块头。

2,3 在这两种情况下最好传递引用。在第二种情况下是const引用。优点是节省不调用构造函数的开销。


如果对象比那个大(很可能是这样)呢? - Luchian Grigore
我曾经遇到过指针占用空间不可忽略的实际情况。虽然这并不是每天都会发生的场景,但确实可能会发生。 - Kirill Kobelev
@Gorpik 你在比较指针占用的空间和完整对象占用的空间吗?间接访问不是问题,直接访问或通过指针访问都是一样的。 - Luchian Grigore
@LuchianGrigore:你仍然需要构造对象。唯一的空间差异是指针的大小。 - Gorpik
1
@Gorpik,你知道向量会复制对象吗? - Luchian Grigore
显示剩余2条评论

1
我应该存储实例数组还是指针数组?为什么?
我更倾向于使用实例数组。这样可以更轻松地管理内存,如果您动态填充指针数组,则可能在第1000个元素处遇到OOM - 现在程序必须清理一半的未完成数组。从这个意义上说,最好一次性预分配一个大块。
如果我不想改变实例,我应该将实例传递给函数还是指向它的指针?为什么? 如果我确实想改变实例,我应该传递指针还是引用?为什么?

我总是更喜欢使用引用,因为这样你就可以跳过检查其有效性。维基百科参考文献不能为空,但我可以在代码中随意使用NULL引用,因为行为是未定义的,在我的编译器中它只是这样发生的。但如果你考虑到这一点,你真的处于权力的黑暗面。或者,声明函数接受指针会导致传递NULL,你必须始终检查它。有一个很好的博客描述了空引用 - 这是一个很好的例子,改变了我的想法。

使用引用的一个缺点是,你无法从函数调用行的代码中判断函数是按值传递还是按引用传递参数。

你应该记住,引用本质上是指针,引用类型也有大小,所以如果你的Class非常小,那么你可能考虑通过值传递对象,如果你不想在函数内部改变它们。不过我仍然会通过引用传递 - Class可能会增长,然后你就需要去修复所有的原型 - 不太好。


"数组的实例需要在您的类中提供默认构造函数和赋值运算符。" - Luchian Grigore
@LuchianGrigore 为什么不行?请详细说明。VisualStudio 报错 'C2512: 'A' : no appropriate default constructor available'。我们是在谈论某种奇怪的数组初始化方式吗? - Roman Saveljev
你可以使用非默认构造函数初始化数组对象,确实可以。为什么这很奇怪呢? - Luchian Grigore
是的,你说得对:A a[2] = {A(5), A(6)};。感谢分享你的智慧。我并不是有攻击性的意思。 - Roman Saveljev
为什么要担心传递空指针?首先,你必须实际这样做(即你的代码中必须存在严重的错误),即使这样做,最有可能的结果是几乎立即出现段错误/AV - 容易调试。在应用程序中可能出错的所有事情中,某个地方传递NULL指针的问题是微不足道的。 - Martin James
@MartinJames 因为函数原型允许你这样做。这并不总是一个错误。如果实际上不期望出现 NULL 指针,则可以放置 assert,但这需要额外的努力。 - Roman Saveljev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接