-2

I am newbie to C++. This pointer topic is complicated for me. I need help on this one. This is my header file

class Stack
{
public:
    Stack();
    ~Stack();
    void push(StackItem input);
    StackItem* pop();
    StackItem* top();
    bool IsEmpty();
private:
    StackItem* head;
};

And this is my cpp file

void Stack::push(StackItem input) {
   if (!IsEmpty()) {
        input.next = head;
        *head = input;
    }
    else {
        *head = input;
    }
}

And this is the IsEmpty Method

bool Stack::IsEmpty() {
    return head == NULL;
}

I got error when call push method. The error is "this->head was nullptr." In the class StackItem* head is defined with class pointer(My instructor did that) I don't know why. I can't compare head variable with the one i got from outside input . Any help would be appreciated

Edit: Where i call this push method is from here(I got a class named Calculator,i call push from its constructor). Calculator.cpp file

Calculator::Calculator(string input)
{
    infixExpression = input;
    stack = new Stack();
    for (int i = 0;i < input.length();i++) {
        char c = input[i];
        std::string s(1, c);
        StackItem *temp = new StackItem(s);
        stack->push(*temp);
    }

}
  • It looks as if `Stack` is actually a singly linked list and that `StackItem` is the node type in that list. Is that correct? – Ted Lyngmo Mar 29 '21 at 21:30
  • You have two problems. The one you have run in to is the `*head` in `*head = input;` says get me the value `head` points at. `head` doesn't point anywhere usable, so the program fails. What you ALMOST want is `head = &input;`, point `head` at `input`. BUT `input` has been automatically allocated and, like any automatic variable, will go out of scope at the end of its code block, the end of the function in this case, and be destroyed and freed, leaving you with a pointer to an invalid object. Much badness will result at some point in the future when the program tries to use this invalid object. – user4581301 Mar 29 '21 at 21:32
  • With just a little more context in the question we can suggest the best way to get around this problem. Give [mre] a read-through and use it as inspiration to rework the example code in your question. Quite often just the act of making a MRE will lead you to the answer without any help, so if you make a MRE early in the question writing process you'll find a lot of the time you won't have to ask the question. – user4581301 Mar 29 '21 at 21:34
  • @TedLyngmo Yes you are correct – Oğuzcan Şirolu Mar 29 '21 at 21:44
  • Ok i'll add more data – Oğuzcan Şirolu Mar 29 '21 at 21:46
  • 1
    @OğuzcanŞirolu Ok, then I suggest hiding `StackItem` from the user. What is the type of the value `StackItem` carries (a part from the pointer to the next `StackItem`)? That's the type you should use in your `push` function - and use the value passed to `push` to create a `new StackItem`, carrying the value. – Ted Lyngmo Mar 29 '21 at 21:47
  • @TedLyngmo StackItem's constructor takes string as argument. It carries a string value. My Instructor wants this push method exactly like this. I mean it should be "void push(StackItem input). can't change this method – Oğuzcan Şirolu Mar 29 '21 at 21:52
  • 1
    @OğuzcanŞirolu That's unfortunate. It'd be more like the standard library if your `push` actually took a `std::string` as an argument and that the `StackItem` was not even known to the user of the `class`. [Example](https://godbolt.org/z/YTxPK1dTW) – Ted Lyngmo Mar 29 '21 at 22:03
  • 1
    That is unfortunate. I would make absolutely certain you haven't accidentally left out a `*`. If you haven't, confirm this with the instructor because it's batshit nuts. `void Stack::push(StackItem * input)` Is the only sane way to pass in a `StackItem` in this case. Odd that Ted and I used almost the exact same starting wording. – user4581301 Mar 29 '21 at 22:03
  • Ok thanks a lot guys, now i will try what @user4581301 said. I'll add pointer to that input in push method. – Oğuzcan Şirolu Mar 29 '21 at 22:14
  • Since `StackItem` is (implicitly) constructible from a `std::string` you _can_ keep the signature of `push` _as-is_, but then you need to do `input.next = head; head = new StackItem(input);` to copy `input` into a heap allocated node. – Ted Lyngmo Mar 29 '21 at 22:20

1 Answers1

0

You have two problems.

Problem 1 is the one you have run in to is the *head in *head = input; dereferences the pointer. *head = input; says "Get me the value head points at and assign input to this value." head doesn't point anywhere usable, so the program fails. What you ALMOST want is head = &input;, point head at input.

But here comes problem 2! input has been automatically allocated and, like any automatic variable, will go out of scope at the end of its code block, the end of the function in this case, and be destroyed and freed, leaving you with a pointer to an invalid object. Much badness will result at some point in the future when the program tries to use this invalid object.

"Wait a second," you say. "I dynamically allocated that sucker with

StackItem *temp = new StackItem(s);

Yes. Yes you did. But similar to above the *temp in

stack->push(*temp);

means "Get me the value temp points at." This value is then passed by value, and copied, into push. This copy goes out of scope at the end of the function. The original pointed at by temp is leaked when temp goes out of scope at the end of the function. Without a pointer to a dynamic allocation, it is next to impossible to find and free the allocation later, and if you don't free unused memory, it sits around taking up space until (assuming the program is running on a modern operating system) the OS cleans up all of the program's memory when the program exits.

One possible solution is

void Stack::push(StackItem * input) // pass in a pointer to a dynamically allocated StackItem
//                         ^ passing by pointer
{
    input.next = head; // just do it. It's always going to be safe, or you have a bigger bug 
                       // coming, and the test to see if you NEED to do it may cost more than 
                       // doing it.
    head = input;
}

and is called something like

StackItem *temp = new StackItem(s);
stack->push(temp);
//          ^ no dereference

Before you read any further, remember: Do what the teacher asks and pass the course. You may have to learn to program for real later, but you have a piece of paper that says you know how to program . Society puts a lot of weight on that piece of paper. And maybe the teacher is setting up mistakes so that they can be covered in detail later or allowing them to happen to keep the amount of information they have to shovel into your brain all at once as light as possible. Hard to say.

My personal preference looks more like this:

void Stack::push(const std::string & input) // reference to string that will not be modified.
{
    head = new StackItem{ input, head }; // aggregate initialization
}

and usage:

stack.push(s);

To make this work,

  1. stack is not dynamically allocated. Rather than
    stack = new Stack(); // Whoops! Leaked any previous Stack!

You use

    Stack stack;

In C++ you should use new as infrequently as possible so you don't have as much of a mess to clean up. Everything you new, you need to delete or you have leaks. In addition to not freeing memory, another common bug is freeing memory too soon. It is hard to manually manage memory, even for the experts. So do what the experts do: Not do it. They use automatic allocations or automatic allocations of a class that manages/owns a dynamic allocation (or other non-memory resource like a file or a mutex). See Resource Allocation Is Initialization (RAII) for more information.

  1. StackItem becomes a private nested struct hidden inside Stack so no one outside knows it even exists. A struct is just like a class in every way you're ever likely to notice except classes default to private access and struct defaults to public. Since no one can see a private nested class except the class that contains it, encapsulation is maintained so long as the nested class is just a simple grouping of data and requires no special defenses from misuse by the class it is nested in. When a class is this simple, you generally don't need a constructor and can take advantage of simple Aggregate Initialization.
class Stack
{
private:
    struct StackItem
    {
        std::string value
        StackItem * next;
    }
    // rest of Stack
};

In a good Object Oriented system, the user knows nothing of what's happening inside the object. They don't know there's a linked list inside. All they know is the call push on it with a string and the string goes onto the stack. This way you can perform a wholesale rewrite of the internals of Stack, maybe base it on a dynamic array, and no one outside will ever know. They just push strings and when they pop, they get strings.

As soon as you expose an inner implementation detail like StackItem, the user knows more about your class, and the more they know, the more resistant to change they become. If you rewrite Stack such that StackItem changes, users of Stack must also change. Better that they not be exposed to StackItem in the first place.

user4581301
  • 33,082
  • 7
  • 33
  • 54