Abstract Data Type: Stack implementation in C++
It seems a stack is the HelloWorld of C++ classes and memory management :).

First, your class has no destructors. It means that when your instance will be destroyed, it will only deallocate the stack attributes : top, level and max_size. Notice that it deallocate the pointer top, but not the object pointed by top ! You must define a destructor : ~Stack(), which will likely consists in a loop executing pop() while the stack is not empty.



Remember that when you are encapsulating pointers, you must respect the Rule of Three (known also as Rule of Five) : it is a simple but accurate rule which can be interpreted as :


  If you need to specify one of destructor/Copy Constructor/Copy Assignment Op; then you probably have to define all three.


Here, it applies because you are holding a pointer, whose value is deleted in the destructor. So if you do the default copy, for instance Stack&lt;T&gt; s2 = s1;, it will copy the data members of s1 into s2. It means that s2.top == s1.top. So when one get destroyed, for instance s1, it will delete s1.top, but then s2.top points to deleted memory : ERROR.



While you are defining copy-constructor and copy-assignment, you may want to define move-constructor Stack(Stack&amp;&amp; other) and move-assignment Stack&amp; operator=(Stack&amp;&amp; other). T&amp;&amp; means "Movable T" and signifies "This value is temporary, so there is no problem if it is altered". For example, here, it means you can only swap other.top and this-&gt;top, so you do not need to copy each node of the stack.
Both move-constructor and move-assignment are parts of the Rule of Five, which is an extent of the Rule of Three above.



You use std::cerr to showing errors. It is not the right way to do this : for example, if push() results in an error, the code calling push has no way to know it. The program will continue to run as usual.

C++ has a specific way to handle errors : exceptions. I will not detail here but follow the link and give it a try.



Moreover, in your pop() function, there is a problem. If the stack is empty, there is no return value ! Your compiler should at least warn you (if not yelling at you) that one conditional-branch has no return value. Instead, you should throw an exception, or, if it is intended, use std::optional&lt;T&gt;. std::optional can represent two states : empty, or full. If it is full, you can access to T value.



Do you know the keyword const in method declaration ? It means it will not modify the object during its execution. Examples :

bool method_A() const; //declaration
int method_B() const { /* definition */ }


You need the keyword const in both declaration and definition. Currently, the functions which can be marked const are display(), isEmpty(), isFull(), getLevel() and getMaxSize().

One of the purpose is the const-ref argument. For example :

template&lt;typename T&gt;
void printLevel(Stack&lt;T&gt; const&amp; stack) {
    std::cout &lt;&lt; "Nb levels = " &lt;&lt; stack.getLevel() &lt;&lt; std::endl;
}


The argument type is a const-ref (one &amp; means reference). It means that, because it is a reference, the argument is not copied, but you can not modify it : "read-only" (because of const). Having a const-ref variable means you can only access to const methods.



Also, you can optimize memory management by using smart pointers. I will not explain this here, but please check out my answer to another stack implementation, which details how can smart pointers be used.

Something you should do is defining max_size constant :

public:
    const int max_size;


Notice that this is ok to set max_size public, because it is a constant and it won't be modified by external code.