0

I am new to c++(have a java background) and thus pointers are sort of new to me. I am dealing with an array of pointers where each index points to an object on the heap as so:

Deck::Deck()
{
seed = rand()%100; //this will be used in shuffle method
srand(seed); 
for(int i=0;i<deckSize;i+=3) //deckSize=12 in this case, p defined as CardTypes* p[deckSize]
{
    p[i]= new Infantry(); 
    p[i+1] = new Artillery();
    p[i+2] = new Cavalry();

}

} All 3 of these classes are subclasses of the class CardTypes(which was only created so I could store diff types in an array).

class CardTypes
{
public:
virtual string getCard() = 0;
virtual ~CardTypes() {};
};
class Infantry: public CardTypes
{
const string name = "Infantry";
public:
string getCard(); //this simply returns "name" so that I can differentiate each object in the array by a data value
};
class Artillery:public CardTypes 
{
const string name= "Artillery";
public:
string getCard();
};
class Cavalry:public CardTypes
{
const string name = "Cavalry";
public:
string getCard();
};

Although not a great way to do it, I have created another array of pointers(CardTypes* s[deckSize) which copies pointers from p into s randomly(thus mimicking a shuffle in a deck of cards):

void Deck::shuffle() //this is the method that puts objects in s to be grabbed in draw()
{
int j = 0;
int k = 1;
int l = 2; //initial setup(index 0 will have Infantry, index 1 will have Artillery and index 3 will have Cavalry and this pattern continues throughout p)
int n = rand()%3 + 1; //gives random # between 1 and 3 1=infantry,2 = artillery,3 = cavalry
int i=0; //counter for loop
while(i<deckSize)
{
     n = rand()%3+1;
    if(n==1)
    {
        if(j>9) //means no more infantry cards as due to pattern of p 
        infantry cards stop after index 9
        {
            continue; //used to reset loop foranother iteration(will get random number,I know this is bad for time complexity)
        }
        else
        {
        s[i] = p[j]; //copy "Infantry" pointer to s
        j+=3;
        i++;
        }
    }
    else if(n==2)
    {
        if(k>10)//means no Artillery cards due to pattern in p
        {
            continue;
        }
        else
        {
            s[i] = p[k];//copy "Artillery" pointer to s
            k+=3;
            i++;
        }
    }
    else
    {
        if(l>11) //means no more cavalary cards due to pattern in p
        {
            continue;
        }
        else
        {
            s[i] = p[l]; //copy "Cavalry" pointer to s
            l+=3;
            i++;
        }
    }
}
}

Now my issue is i am trying to create a draw method that grabs a pointer from s and returns it. My program completely crashes when I attempt this and I am not sure why:

CardTypes* Deck::draw() //draws a card from the deck and returns it
{
CardTypes* card = s[deckSize];
delete s[deckSize];//clear heap
s[deckSize] = NULL;//remove what pointer was pointing too (as card has been drawn)
deckSize--;
return card;
}

I then attempt to call this method:`

int main()
{
Deck d1;
d1.shuffle(); //this works
d1.getCurrentDeck();//this works, just prints out each objects getCard() method in s
CardTypes* card = d1.draw();//does not cause a crash
cout<<"Card:"<<card->getCard() <<"\n";//crashes here
}

This issue is probably due to my inexperience with pointers but any help would be appreciated. Also note I delete the arrays after I am done with the program using delete [] p and delete [] s, I have not included this in the code as it is not of issue right now.

JmanxC
  • 377
  • 2
  • 16
  • I thought Java had pointers. Since you are coming from Java let me give you this advice: Never use `new` in C++. Your code will become much simpler and better if you follow it. – nwp Sep 26 '17 at 00:44
  • The shown code fails to meet the requirements of a [mcve], as explained in the [help]. You need to edit your question, review what the [help] explains about creating a [mcve], and edit your question accordingly. – Sam Varshavchik Sep 26 '17 at 00:45
  • I believe your issue is in your `Deck::Draw()` method. You are creating a pointer to `s[decksize]`, which is simply a reference to this object in memory. You are then deleting this object from memory in the following two lines, which invalidates your `card` pointer. Hopefully you can see why, creating a pointer simply creates a reference to an object, not another instance of the object itself. – Ryan Turnbull Sep 26 '17 at 00:50
  • 2
    What you are struggling with is ownership. Here, `CardTypes* card = s[deckSize];`, `card` now points to the same thing as `s[deckSize]`. That means `delete s[deckSize];` destroys and frees what `card` is pointing at, too. You have to decide who owns what and is responsible for `delete`ing it. `std::unique_ptr` will help you a lot here. – user4581301 Sep 26 '17 at 00:51
  • @Ryan Tunbull I did think of that, how then would i remove the reference after returning it then? As once i grab the pointer i wish to remove it from s(also removing the object it points to on the heap). – JmanxC Sep 26 '17 at 00:51
  • You could simply return a CardType object instead of a pointer to one? Unless you were wanting to avoid this for whatever reason. – Ryan Turnbull Sep 26 '17 at 00:53
  • @Ryan Turnbull also an interesting fact,if i comment out the delete and = null section of code it still crashes – JmanxC Sep 26 '17 at 00:54
  • @JmanxC: Use a [`std::vector`](http://en.cppreference.com/w/cpp/container/vector) instead of a raw array. You can easily remove elements from a `std::vector` using its [`erase()`](http://en.cppreference.com/w/cpp/container/vector/erase) method. If you stick with a raw array, then look at the [`std::remove()`](http://en.cppreference.com/w/cpp/algorithm/remove) algorithm. – Remy Lebeau Sep 26 '17 at 00:55
  • @JmanxC then i'd recommend debugging your code with breakpoints and see exactly where the crash is occuring. Could be in your `getCard()` method. – Ryan Turnbull Sep 26 '17 at 00:59
  • @JmanxC -- I'll let you in on a secret -- putting in comments for pointered up code like "this works" may be fools gold. You may still be mismanaging pointers, yet the code may seem to work. C++ is not Java -- you make a mistake with pointer manipulation, there is no guarantee your program will crash, give you some equivalent of a stack trace, etc. Your program may even "work" with the bug being hidden. – PaulMcKenzie Sep 26 '17 at 01:06

2 Answers2

0

Your problem is you are deleting the instance and, after that, you want to use it.

You are creating many instances:

for(int i=0;i<deckSize;i+=3) //deckSize=12 in this case, p defined as CardTypes* p[deckSize]
{
    p[i]= new Infantry(); 
    p[i+1] = new Artillery();
    p[i+2] = new Cavalry();

}

The size of your array is determined by the variable deckSize.

In your function Deck::draw() your have some errors:

  • You are set a pointer of an instance of CardType with this code: CardTypes* card = s[deckSize]; But the array s has an index base 0, so s[deckSize]is accessing another memory sector that is not assigned to array s (Could a Memory Access Violation). use s[deckSize-1] instead of s[deckSize]..

  • You are release the memory that was assigned to pointer card and this pointer is returned to be used outside the function, which try to use this instance but is doesn't exist any more. So the memory which card and s[deckSize] share is released. Don't forget that s[deckSize] could raise a Memory Access Violation.

Check your code:

CardTypes* Deck::draw() //draws a card from the deck and returns it
{
  CardTypes* card = s[deckSize-1];  //Assign the pointer to card.
  delete s[deckSize-1];//DELETE THE INSTANCE (The memory that 

  return card; //The card points to a memory previously released.
}

Here is the moment that you are trying to use an unallocated:

CardTypes* card = d1.draw();//Get the pointer to s[deckSize-1]
cout<<"Card:"<<card->getCard() <<"\n";//crashes here

UPDATE:

Answer your comment, You can do this:

1.- Get the referencer to instance: CardTypes* card = s[deckSize-1];.

2.- Set the slot of your array in NULL and decrease the index:

s[deckSize-1]=NULL;
deckSize--;.

3.- Return de reference saved in card to upper level: return card;.

4.- Use the reference returned as you need it:

CardTypes* card = d1.draw();
cout<<"Card:"<<card->getCard() <<"\n";.

5.- Finally, delete de instance once you have finished to use it, for example just after the invocation of getCard():

cout<<"Card:"<<card->getCard() <<"\n";
delete card;

It's important to say that you decide where and when reserve, use and release the memory; just keep in mind to do it in an organized way and apply best practices, like these:

https://www.codeproject.com/Articles/13853/Secure-Coding-Best-Practices-for-Memory-Allocation http://www.embeddedstar.com/technicalpapers/pdf/Memory-Management.pdf

Jorge Omar Medra
  • 978
  • 1
  • 9
  • 19
  • How would you suggest I return a pointer then delete it after? With how my code is I need to use an array of pointers but once an item is returned via draw(), I would like to clear memory(ie delete the object) and remove this pointer from the array. In java(although not anywhere close to the same thing) you could save an int for example from an array,clear the int at that index and return the copy. I am trying to do the same with pointers. The only thing i can think of is to return it and use some other function to clear the array after. – JmanxC Sep 26 '17 at 01:47
  • I made an upgrade to answer your comment, check it. – Jorge Omar Medra Sep 26 '17 at 04:50
0

You are struggling with pointer ownership. You understand that in C++ one must delete a pointer when it is no longer needed, but in Deck::draw you delete a pointer when it is still needed.

CardTypes* Deck::draw()
{
    CardTypes* card = s[deckSize]; // s and card point to same allocation
    delete s[deckSize]; // boom! card points to garbage.
    s[deckSize] = NULL;
    deckSize--;
    return card;
}

You can use raw pointers, but you need to do it with a lot of coding maturity and deliberation.

Or you can say Smurf it and protect yourself from accidents like this with smart pointers. What is a smart pointer and when should I use one?

std::unique_ptr bundles ownership of a pointer. Only one std::unique_ptr is allowed at a time. You can't copy it. It as to be moved everywhere, transferring ownership from one holder to the next. But in the end there can be only one. You have to go out of your way to be stupid with a unique_ptr. Making five unique_ptrs and pointing them all at the same pointer, yeah you can do that. Put a self-destructing Automatic variable in a unique_ptr, yeah you can do that (and sometimes you do, but with a custom deleter that does nothing).

unique_ptr is the owner of the pointer. Whoever has the unique_ptr is owner by proxy because as soon as they get rid of the unique_ptr, the pointer goes with it.

Let's take a look at what we can do with a std::unique_ptr<CardTypes> to corral these wild pointers. If s is an array of unique_ptrs, std::unique_ptr<CardTypes> s[MAX_DECK_SIZE];, draw becomes

std::unique_ptr<CardTypes> Deck::draw()
{
    std::unique_ptr<CardTypes> card = std::move(s[deckSize]);
    // delete s[deckSize]; don't. Card now owns the card
    // s[deckSize] = NULL; handled by moving ownership
    deckSize--;
    return card;
}

This can be simplified to

std::unique_ptr<CardTypes> Deck::draw()
{
    return std::move(s[deckSize--]);
}

decksize-- is a post decrement so it happens after and the rest of the work is managed by the unique_ptr when it is moved out of s;

Sadly this means

s[i] = p[j];

ain't so easy anymore. You need

s[i] = std::move(p[j]);

But only if p no longer needs its jth element, because s[i] owns it now, baby.

Too little information has been provided in the question to wrangle this properly, but...

It's very possible that you could keep p full of unique_ptrs and load s with raw pointers whose lifespan is governed by p and pass s's pointers around naked and free without ever deleteing them because you know p has your back. So long as you keep p around longer than s and whoever s gives pointers to. It all comes back to ownership and in this case p owns all the Cards.

That turns

s[i] = std::move(p[j]);

into

s[i] = p[j].get();

and draw into

CardTypes * Deck::draw()
{
    return s[deckSize--];
}

and makes life really, really easy.

user4581301
  • 33,082
  • 7
  • 33
  • 54