代码超出范围。如何动态分配这个向量?

时间:2017-07-29 13:09:09

标签: c++ c++11 vector

我对c ++很新,并开始编写一个小型扑克游戏,以帮助我获得更多的矢量体验。我在第二个函数中遇到运行时错误,在阅读下面的帖子并发现我误解了reserve()类成员的行为后,这并不奇怪。我相信我可以通过resize()来解决这个问题,但是我希望能够以一种方式重新构建这个函数来动态分配。我在动态分配方面遇到的困难在于,我正在努力使交易功能将牌分发给每个玩家,就像真正的纸牌游戏一样(是的,我意识到这在某种程度上是统计上多余的)。我可以很容易地用c中的指针数学做这个,我想我可以通过静态分配这些向量中的一些来解决这个问题,但我更愿意尽可能优雅高效地使用c ++。关于动态执行此操作的方法的任何建议?也就是说,如何完全避免为deal()函数中使用的容器分配或保留空间?

Choice between vector::resize() and vector::reserve()

#include <cassert>
#include <algorithm>
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>

const int __handSize__ = 5;

class Hand {
public:
    std::vector<int> held;
    std::vector<int> played;
};

std::vector<int> shuffle() {
    std::vector<int> deck;
    for( int i = 0; i < 52; i++ ){
        deck.push_back( i );
    }
    std::random_shuffle(deck.begin(), deck.end());
    assert( 52 == deck.size() );
    return deck;
}

std::vector<Hand> deal( int nPlayers, std::vector<int> &deck ) {
    std::vector<Hand> playerHands;
    playerHands.reserve( nPlayers );
    for( int i = 0; i > __handSize__; i++ ) {
        for( int j = 0; j < nPlayers; j++ ) {
            playerHands.at(j).held.push_back( deck.back() );
        }
    }
    return playerHands;
}

int main() {
    srand(time( NULL ));
    std::vector<int> deck = shuffle();
    std::vector<Hand> hand = deal( 3, deck );
    std::cout << hand.at(1).held.at(1) <<std::endl;

}

输出:

dmf86@tux3:~/src/poker$ ./poker
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 1) >= this->size() (which is 0)
Aborted

回溯:

#0  0x00007ffff74aa428 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff74ac02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ae484d in __gnu_cxx::__verbose_terminate_handler() ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7ae26b6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7ae2701 in std::terminate() ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ae2919 in __cxa_throw ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007ffff7b0b3f7 in std::__throw_out_of_range_fmt(char const*, ...) ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7  0x000000000040218c in std::vector<int, std::allocator<int> >::_M_range_check(unsigned long) const ()
#8  0x0000000000401785 in std::vector<int, std::allocator<int> >::at(unsigned long) ()
#9  0x0000000000401030 in main ()

3 个答案:

答案 0 :(得分:3)

保留或调整向量的厌恶是什么?当我知道向量需要的大小时,我总是保留或调整大小。这个选择取决于我打算如何填补它。

特别是对于大型向量,将大小保留为适当的值是一种更好的做法,而不是允许它随着它的增长而继续调整大小(它必须这样做)。

我认为没有理由不将牌组保留或调整到52,将牌子调到5,因为这是他们在使用时必须增长的。

如果您打算使用push_back来填充向量,请保留它们。如果您打算使用作业,请将其调整大小。

e.g。

vector<int> deck;  // create empty vector
deck.resize(52);   // resize to 52 elements
// now size = 52, and capacity = 52

for(int i = 0; i < 52; i++)
    deck[i] = i;   // assign the elements

或:

vector<int> deck(52);  // create vector with 52 elements
// size = 52 and capacity = 52

for(int i = 0; i < 52; i++)
    deck[i] = i;    // assign to the elements

或:

vector<int> deck;
deck.reserve(52);    // reserve space for 52 elements
// size = 0, capacity = 52

for(int i = 0; i < 52; i++)
    deck.push_back(i);  // push to create the elements
// now size = 52, capacity = 52

答案 1 :(得分:1)

无论何时添加或删除元素,向量都不会重新分配。分配的大小可能与使用的大小不同 - 正如您已经注意到reserve()resize()之间的差异。随着向量大小的增加,分配变得更大,因此分配的数量会减少&#34;松弛&#34;是一个常数因子(IIANM)乘以向量的长度。因此,对于n次插入,您只进行O(log(n))重新分配调用。类似地,当您删除元素时,仅当大小与保留大小的比率低于某个点时才会取消分配空间 - 如果有的话。

修改:在澄清之后 - 关于不希望任何调整大小或保留 - 可能的选项可能是使用地图而不是矢量。 std::mapstd::unordered_mapoperator[],如果缺少&#34;插入,否则更新&#34;语义。事实上,我还会添加一些更改,并提出一个&#34;交易&#34;功能:

auto deal(const SomeKindOfContainer& players, std::vector<Card>& deck) {
    std::unordered_map<Player, Hand> playerHands;
    auto handSize = deck.size() / players.size();
    for(int i = 0; i < hand_size; i++) {
        for(const auto& player : Players) {
            playerHands[player].held.push_back( deck.pop_back() );
        }
    }
    return playerHands;
}

这里的好处是它并不假设玩家是整数;而且,事实上,也许你会改用字符串。玩家可以是std::vectorstd::unordered_set

此外,使用地图或集合会浪费&#34;,因为它们使用指针并执行许多分配;所以,如果你正在研究数以百万计的玩家和卡片,我会提出别的建议。但在你的例子中,效率并不是真正的问题。

PS - 正如@PeterBecker所提到的,C ++标准只需要分摊的常量时间插入,而不是我所描述的所有内容 - 这是对库实现通常具有的描述。 < / p>

答案 2 :(得分:0)

我设法找到了我自己的问题的解决方案,并认为我应该分享。感谢swift Aziuth的投入。它远未完成,甚至功能,但这将很好。我决定跟踪一个玩家是否折叠并因此不再活动是有用的,所以我添加了一个布尔成员&#34; active&#34;到手班。这有一个额外的好处,允许我强制Deck :: deck向量初始化空结构而不必手动调整它们。

我还把它分成了三个类,以保持每一个小而简单。

class Hand {
public:
    std::vector<int> held;
    std::vector<int> played;
    bool active;
};

class Deck {
    const int _handSize_ = 5;
    std::vector<int> deck;
public:
    void shuffle();
    int getCard();
};

class Players {
    std::vector<Hand> playerNo;
    int activePlayers;
public:
    Players( int n );
    void addCard( int player, Deck &deck );
    bool isActive( int n );
};