【问题标题】:Should I use a const reference or a boost::shared_ptr?我应该使用 const 引用还是 boost::shared_ptr?
【发布时间】:2009-11-11 14:09:57
【问题描述】:

我创建了一些 C++ 类来模拟纸牌游戏作为学习练习。

我有 SolitaireGame、CardStack(棋盘上 10 叠卡片之一)和卡片的课程。我当前的模型指出,SolitaireGame 拥有一个包含 104 个卡片对象的向量——我称之为“鞋子”。 SolitaireGame 还跟踪 10 个 CardStack,它们本质上是存储在牌盒中的 Card 对象地址的双端队列。 Deck 和 Hand 继承自 CardStack。我通过指向存储在 Shoe 中的原始对象的指针将卡片从 Deck 传递到 Hand 到 Cascade。

根据我收到的对this 问题的一些回答,我不应该通过指针传递卡片,而应该使用 const 引用。原因是存储在向量中的对象可以移动它们的地址,因此将它们的地址存储在任何地方都是禁止的。我最近开始研究 boost::sharedptr。人们对这里使用 shared_ptr 到 Card 有何看法?

以下是这些类的简化版本:

class SolitaireGame
{
    public: 
    SolitaireGame::SolitaireGame( int numsuits );       

    private:        
        vector<Card> _shoe;
        Deck _deck;
        Hand _hand;
        CardStack _cols[NUM_COLUMNS];
        int _numsuits;
        GameState   gamestate;
 };

class CardStack
{
    public:
        CardStack(){ cout << "CardStack constructor" << endl; }
        CardStack( const CardStack& );
        CardStack( const deque<Card *> &d );
        ~CardStack(){ }

        virtual Card * PullCard( Face f );
        virtual void PushCard( Card * c );

        Card * CardAt( int i ) const;
        Card * Top() const;

        deque<Card *>::iterator Begin() { return _cards.begin(); }
        deque<Card *>::iterator End() { return _cards.end(); }

        int Size() const;
        CardStack& operator=( const CardStack& rhs );

        friend std::ostream& operator<<(std::ostream &os, const CardStack &obj);

private:
        deque<Card *> _cards;

};

【问题讨论】:

    标签: c++ boost reference


    【解决方案1】:

    原因是存储在向量中的对象可以移动它们的地址,因此将它们的地址存储在任何地方都是不行的。

    出于同样的原因,存储 (const) 引用与存储指针一样糟糕。如果只要其他对象持有指向其中对象的指针,向量的大小就不会改变,那么您应该是安全的。

    在使用 C++ 编程时,您应该始终决定谁“拥有”一个对象,例如谁有责任在不再需要时将其删除。如果没有自然对象所有者,您可以求助于像 boost::shared_ptr 这样的智能指针,它使用引用计数或垃圾收集来管理对象的生命周期。

    在您的情况下,很明显 SolitaryGame 实例拥有所有卡。此外,游戏中的卡牌数量是固定的。因此,您可以轻松地将卡片的指针传递给依赖于游戏实例的对象。

    一旦游戏被删除,所有卡牌都将被删除,剩余指针无效,但此时其他持有卡牌指针的对象也应该被删除。

    【讨论】:

    • 感谢费丁丹的回复。是的,这就是我设想它的工作方式,其中一个游戏被删除,卡片被删除,但没关系,因为所有 Cascades 也被删除。我认为我的代码是相当安全的,因为卡片不是动态分配的,而且鞋子总是包含 104 张不会改变的卡片。
    • 我完全同意这一点 (+1)。我唯一要补充的是,我会将卡片作为 (const) 引用传递(但将它们作为指针存储在 CardStack 中)。我的策略是当且仅当 NULL 是有效值时,函数参数和返回值才是指针,否则使用引用。这省去了在使用前检查/断言卡是否有效的需要,并让程序员清楚地知道 NULL 是无效的。
    【解决方案2】:

    是的,如果您要获取您的元素的地址

    vector<Card> _shoe;
    

    并将它们放入您的

    deque<Card *> _cards;
    

    正如您所描述的,肯定有问题。您的向量可能会重新分配,导致向量的 Card 元素的地址不再有效。

    传递对向量内容的引用(const 或其他)与传递指针的问题相同。在 C++ 中,引用实际上是一个隐蔽的指针。与指针的唯一区别是它的使用方式(作为别名)它不能被“取消”,即设为 NULL,以及它与别名类型无法区分的事实(你不能有卡片参考向量)。引用没有任何特殊的引用计数或其他垃圾收集语言所获得的任何东西。所以当你的向量重新分配时,如果有人持有对牌组中任何一张牌的引用,这些引用就会像指针一样容易失败。

    用 boost::shared_ptr 的卡片向量替换你的向量可以解决你的问题。 boost::shared_ptr 是引用计数的。这意味着它会跟踪底层对象存在多少引荐来源网址。您的向量将是 shared_ptrs 的向量,而不是对象本身的向量。因此,当向量重新分配时,您只是在重新分配期间暂时将新的引用者添加回底层对象,然后向量将 shared_ptr 替换为位于重新分配空间中的 shared_ptr。底层对象不动。

    我会更进一步,建议不要给每个人一个 shared_ptr。将boost::weak_ptr's 传递给非所有者。 boost::weak_ptr 是对基础数据的弱引用。当需要时,weak_ptr 为某人提供了获取 shared_ptr 的句柄。它不参与基础数据的引用计数。因此,您可以首先检查底层数据是否被所有者删除。然后,如果它没有被删除,则获取一个 shared_ptr(暂时参与引用者计数)并执行所需的操作。

    【讨论】:

    • 感谢道格的回复。所以,如果我对你的理解正确,你的建议是作为额外的预防措施(即使鞋子非常稳定)可以将鞋子创建为 shared_ptrs 的向量来动态分配的卡片?任何希望访问这些卡片的人都应该通过弱指针来访问。我喜欢这个主意,我认为这对我来说是一个很好的学习练习。
    【解决方案3】:

    您不能将引用存储在容器中,因此如果您想要共享访问,则只能使用指针。 shared_ptr 似乎有些无意义,因为您不想管理内存。

    但是,如果我创建了一个 Card 类,它将只包含一两个整数,并且它是不可变的(除非这些是可以改变其花色和价值的魔法卡)。因此我只会使用卡片的副本。

    当您返回时,您可能更喜欢引用(除非您想存储指向返回值的指针,否则最终看起来会很奇怪)。但就个人而言,我还是会按价值返回。

    【讨论】:

      【解决方案4】:

      我认为上面费迪南德的回答是可行的方法,但我想评论一下 boost::shared_ptr 在这种情况下的使用。

      您的 Card 对象有多大?如果它们足够小(例如几个整数),那么仅复制卡片本身可能比使用 boost::shared_ptr 更好,因为复制 boost::shared_ptr 并不便宜(由于引用计数上的线程同步)。这是基于您的 Card 对象不需要具有唯一身份的,例如不过,一张黑桃卡片对象和其他任何对象一样好。

      【讨论】:

      • 你能详细说明一个“独特的身份”吗?是的,每张卡片都是一个独特的对象,尽管可能有 4 个单独的 10 个黑桃卡片对象 - 但是这 10 个黑桃对象中的任何一个都不需要知道其他 3 个。卡片对象并不重 - 它们包含 3 个整数值。
      猜你喜欢
      • 2014-03-21
      • 2011-07-31
      • 2013-01-30
      • 1970-01-01
      • 2011-09-13
      • 2023-04-07
      • 1970-01-01
      • 2020-02-17
      • 1970-01-01
      相关资源
      最近更新 更多