在这里使用指针向量是不必要的还是更糟,导致内存泄漏?

时间:2009-10-02 09:02:42

标签: c++ pointers vector

我对C ++编程比较陌生,但我是一名10年的C程序员,因此对指向对象的指针比对对象的引用更为舒服。

我正在写一个纸牌游戏 - 这个设计不安全吗?还有更好的方法吗?

无论如何,我有一个班级SolitaireGame

class SolitaireGame:
{
    public:
        SolitaireGame( int numsuits = 1 );
    private:
        Deck * _deck;
        vector<Card> _shoe;
};

Deck因此被定义:

class Deck:
{
public:
 Deck::Deck( vector<Card>& shoe );
 ~Deck();
 int DealsLeft() const { return deals_left; }
 Card * PullCard();
private:
 int deals_left;
 int num_each_deal;
 deque<Card *> _cards;
};

Deck构造函数引用Card个对象(鞋子,通常是104个卡片)的向量,并将指向每个卡片的指针推到它自己的指针上。

Deck::Deck( vector<Card>& shoe )
{
    vector<Card>::iterator iter = shoe.begin();

    while( iter != shoe.end() )
    {
        _cards.push_front( &(*iter) );
        iter++;
    }
}

}

鞋子是在SolitaireGame构造函数中创建的。一旦创建了动态创建的Card对象的这个向量 - 然后我将对该向量的引用传递给构造函数。

SolitaireGame::SolitaireGame( int numsuits ):_numsuits(numsuits ) 
{
    Card * c;
    vector<Card> _shoe;

    for( int i = 0; i < NUM_CARDS_IN_SHOE; i++ )
    {
        c = new Card();
        _shoe.push_back( *c );
    }

    _deck = new Deck( _shoe );
}

我的想法是,鞋子将是Card对象的实际内存的容器,而DeckColumns只是处理指向这些Card对象的指针。

6 个答案:

答案 0 :(得分:15)

只需使用这段代码,就可以泄漏动态创建的卡片。

Card * c;
vector<Card> _shoe;

for( int i = 0; i < NUM_CARDS_IN_SHOE; i++ )
{
    c = new Card();
    _shoe.push_back( *c );
}

_shoe.push_back( *c )Card指向的c对象的副本添加到Card s的向量中。然后,您无法删除之前在该行中创建的原始Card

分配NUM_CARDS_IN_SHOE Cards的向量可以更简单地实现:

std::vector<Card> _shoe( NUM_CARDS_IN_SHOE );

查看您的卡结构,看起来您对对象之间拥有(或几乎拥有)严格的所有权,因此我认为您不需要动态创建Card

请注意,您的本地变量_shoe正在隐藏类变量_shoe。这可能不是您想要的,因为传递给_shoe构造函数的本地Deck将超出构造函数末尾的范围。

如果您在SolitaireGame中对变量重新排序,则可以执行以下操作:

class SolitaireGame:
{
public:
    SolitaireGame( int numsuits = 1 );
private:
    vector<Card> _shoe;
    Deck _deck;
};

SolitaireGame::SolitaireGame( int numsuits )
    : _shoe(NUM_CARDS_IN_SHOE)
    , _deck(_shoe)
{
}

我已将_deck更改为指针。我使用的事实是成员变量是按照类定义中声明的顺序构造的,因此_shoe将在作为对_deck的构造函数的引用传递之前完全构造。这样做的好处是我已经不再需要动态分配_deck。由于没有new的使用,我知道我不能错过任何delete来电,因为没有必要明确释放。

你是对的,你可以存储指向Card_shoe _deck的指针而没有任何内存管理问题,但请注意,你不能添加或删除任何内存管理问题在游戏生命周期中Card _shoe,否则您将使_deck中的所有指针无效。

答案 1 :(得分:4)

我认为有两个错误:

  1. 执行_shoe.push_back( *c );时,您正在创建Card对象的副本,因此永远不会释放保留给c的内存。顺便说一句,你应该经常检查每个新存在的补充删除。你的删除在哪里?
  2. 在你的Deck构造函数中,你保存指向在堆栈中vector<Card> _shoe;)的对象的指针,所以只要SolitaireGame构造函数结束,它们就会被删除,你的指针也会被删除将无效。编辑:我看到你的班上有另一个_shoe,所以没有必要声明另一个_shoe局部变量,事实上只是没有声明你将解决这个问题。
  3. 我希望这对你有所帮助。

答案 2 :(得分:2)

初步想法:

  1. 在SolitaireGame课程中,您将_shoe声明为:

    vector<Card> _shoe;

    但是在构造函数中,您可以像这样将堆对象推送到它:

    c = new Card();

    _shoe.push_back( *c );

    所以,你需要声明它:

    vector<Card*> _shoe;

  2. 您不会在构造函数中初始化变量,例如Deck类中的deals_left和num_each_deal。我假设你把它留下来不会弄乱代码,但这是个好主意。

  3. Class SolitaireGame创建并拥有Deck对象。它还有一个Deck,指向SolitaireGame的Card对象。这里的所有权尚不清楚 - 谁删除​​了它们?虽然指向多个容器中的对象的指针将起作用,但它可以使调试更加困难,因为可以进行多次删除,使用后删除,泄漏等等。也许设计可以简化。也许让Deck最初拥有Card对象,当它们被移除时,它们会被放入SolitaireGame中的向量中,并且两者不会同时存在。

  4. 在SolitaireGame的构造函数中,您声明另一个卡片向量,它会隐藏类声明中的声明。当你将Card对象推到它上面时,它们将不会被推送到正确的向量,这将超出构造函数末尾的范围,并且你的类成员将为空。只需从构造函数中删除它。

  5. 无论如何,我需要一杯茶。之后我会再看看,看看我是否可以提出其他建议。

答案 3 :(得分:2)

我不认为 new 关键字应该出现在这些类的代码中的任何位置,我不明白为什么你会遇到通过指针共享卡片的麻烦。存储在向量中的项目的地址是灾难的秘诀 - 您需要保证在获取地址后不会对向量进行修改,因为它会在不告诉您的情况下在内存中移动内容。

假设一个Card对象除了一个或两个整数之外不存储任何东西,使用副本和值会更简单。

_deck = new Deck( _shoe );

同样,我没有看到通过动态分配包含两个整数和一个双端队列的对象来增加程序复杂性的最轻微理由。

如果您担心复制某些较大类的成本(我估计这对于感知性能没有影响),那么就不要复制它们,然后通过 const <传递它们/ strong>引用(如果你不需要改变实例),否则是非const引用/指针。

答案 4 :(得分:1)

由于这样的游戏对象总是有自己的套牌,你应该考虑让Deck对象成为SolitairGame中的真正的成员 - 而不仅仅是指针。这将使甲板对象的生命周期管理更加简单。例如,您将不再需要自定义析构函数。请记住,STL容器包含副本。如果你写了像

这样的东西
myvector.push_back(*(new foo));

你有内存泄漏。

此外,存储指向向量元素的指针是危险的,因为指针(或通常的迭代器)可能变得无效。对于矢量,这是需要增长的情况。另一种方法是使用std :: list,它可以在插入,删除等之后使迭代器保持有效。

另外,请记住,在C ++中,结构和类通常会获得隐式复制构造函数和赋值运算符。尊敬rule of three。禁止复制和分配,或确保正确管理资源(包括动态分配的内存)。

答案 5 :(得分:1)

这个程序会泄漏内存,想知道为什么?或者怎么样?

push_back

请记住此调用不会插入您提供的元素,但会创建一个供自己使用的副本。请阅读this了解详情

所以

    Card *c = new Card(); // This is on heap , Need explicit release by user

If you change it to 

    Card c; // This is on stack, will be release with stack unwinding 

复制下面的程序并执行它,{我只是添加了日志记录},尝试使用上面的两个选项

#include<iostream>
#include <vector>
#include <deque>

using namespace std;

const int NUM_CARDS_IN_SHOE=120;

class Card
{
public:
    Card()
    {
        ++ctr;
        cout<<"C'tor callend: "<<ctr<<" , time"<<endl;
    }
    ~Card()
    {
        ++dtr;
        cout<<"D'tor called"<<dtr<<" , time, num still to release: "<<((ctr+cpy)-dtr)<<endl;
    }
    Card& operator=(const Card & rObj)
    {
        return *this;
    }

    Card (const Card& rObj)
    {
        ++cpy;
        cout<<"Cpy'tor called"<<cpy<<endl;
    }
private:


    static int ctr,dtr,rest,cpy;
};
int Card::ctr;
int Card::dtr;
int Card::rest;
int Card::cpy;
class Deck
{
public:
 Deck::Deck( vector<Card>& shoe );
 ~Deck();
 int DealsLeft() const { return deals_left; }
 Card * PullCard();
private:
 int deals_left;
 int num_each_deal;
 std::deque<Card *> _cards;
};

Deck::Deck( vector<Card>& shoe )
{
    vector<Card>::iterator iter = shoe.begin();

    while( iter != shoe.end() )
    {
        _cards.push_front( &(*iter) );
       iter++;
    }
}
class SolitaireGame
{
    public:
        SolitaireGame( int numsuits = 1 );
    private:
        Deck * _deck;
        std::vector<Card> _shoe;
};





SolitaireGame::SolitaireGame( int numsuits )
{
    Card * c;
    vector<Card> _shoe;

    for( int i = 0; i < numsuits; i++ )
    {
        c = new Card();
        _shoe.push_back( *c );
    }

    _deck = new Deck( _shoe );
}



int main() 

{
    {
    SolitaireGame obj(10);
    }
    int a;
    cin>>a;
    return 0;
}