I'm fairly new to C++ and new to pointers as well. I'm currently working on a stack and was trying to reallocate the memory for the stack as the size of the stack reaches the top however, I'm running into issues. I've already done a lot of research both on Google and stack overflow and have found some information helpful but since I'm so new to stacks and C++ I'm still having issues. I was hoping some bright and intelligent people could at least point me in the right direction.
now... Here's my code.
#include <iostream>
#define STACKMAX 20
using namespace std;
template <class T> class StackTemplated {
private:
int top;
T values[STACKMAX];
public:
StackTemplated();
void push(T i);
T pop(void);
bool empty(void);
};
template <class T> StackTemplated<T>::StackTemplated() {
top = -1;
}
template <class T>void StackTemplated<T>::push(T i) {
if (top == STACKMAX - 1) {
// reallocate top of stack. (this is the area I'm having issues)
char * string1;
string1 = (char *)calloc(STACKMAX, sizeof(char));
if (top == STACKMAX - 1) {
cout << "The stack didn't re-allocate.";
exit(1);
}
} else {
top++;
values[top] = i;
}
}
template <class T> T StackTemplated<T>::pop(void) {
if (top < 0) {
printf("%", "Stack underflow!");
exit(1);
} else {
return values[top--];
}
}
template <class T> bool StackTemplated<T>::empty() {
return (top == -1);
}
Here's a list of a few things I noticed:
STACKMAX is a constant. If you're expanding the stack, how will you keep track of how big it currently is?
The values member is a fixed-size array. You won't be able to change the size of it dynamically without changing how this is declared and allocated.
calloc() allocates a new chunk of memory with the number of bytes you specify. You'll need to somehow copy the existing stack into the new memory block, and free the previous one.
You're allocating only STACKMAX bytes in the call to calloc(). You'll probably want to scale this by sizeof T, in case T is not a char.
There will be a lot of details for you to fix up once you address these major points. Good luck.
The problem is that you don't want to reallocate the top of the stack. Rather, you want to allocate a new array of values which is large enough to hold the new values. Also, since you need to reallocate the array, values should be a pointer.
But how about we forget all this. If we're working in c++, let's use what c++ offers us to make our lives easier. After that's done, then try open things up, if you really feel the need.
One of the things I'm referring to is your use of calloc. Using calloc is a bad idea, particularly when using templates. The problem is that since calloc has no type information, it won't do something as basic as calling a constructor. Constructors are very important in OOP, since they guarantee that an object's invariance when it is created. Instead, use the new[] keyword, like
values = new T[STACKMAX];
This allocates an array of T of STACKMAX length. Of course, as Greg points out, you should reconsider the use of STACKMAX, and use a variable instead. Also, values shouldn't be a static array, but should instead have type T*.
Another thing I was referring to is the fact that you are really trying to implement an array which grows dynamically as needed. In c++, we call such a structure a vector. If you use a vector, your entire code reduces to
#include<iostream>
#include<vector>
using namespace std;
template<class T> class StackTemplated {
private:
std::vector<T> vec;
public:
StackTemplated() { } // the constructor is trivial; in fact, you can leave it out if you want
void push(T i);
T pop(void);
bool empty(void);
};
template<class T>
void StackTemplated<T>::push(T i) {
vec.push_back(i);
}
template<class T>
T StackTemplate<T>::pop(void) {
T top = vec.back();
vec.pop_back();
return top;
}
template<class T>
bool StackTemplate<T>::isEmpty(void) {
return vec.size() == 0;
}
That's all. It's a lot less hairy if you can use an existing data structure to implement the new data structure.
Once you get really comfortable with how a vector works (and there's plenty of explanations / documentation on the web), then try implementing the functionality yourself. Bottom line is, implementing a data structure is a lot easier if you know exactly how it's supposed to behave.
I would declare your values like
T* vaules;
Then use new to create it not calloc. You will need to keep track of the top of the stack and size of it. As Greg says when you grow the stack make sure and copy data over and clean up the old one.
Related
I want to create my own game engine so I bought a few books one being Game Engine Architecture Second Edition by Jason Gregory and in it he suggests implementing a few custom allocators. One type of allocator the book talked about was a stack-based allocator, but I got confused when reading it. How do you store data in it? What data type do you use? For example, do you use a void*, void**, an array of char[]? The book says you're meant to allocate one big block of memory using malloc in the begining and free it in the end, and "allocate" memory by incrementing a pointer. If you could help explain this more that would be great because I can't seem to find a tutorial that doesn't use std::allocator. I also thought this might help others interested in a custom allocator so I posted the question here.
This is the header file example they give in the book:
class StackAllocator
{
public:
// Represents the current top of the stack.
// You can only roll back to the marker not to arbitrary locations within the stack
typedef U32 Marker;
explicit StackAllocator(U32 stackSize_bytes);
void* alloc(U32 size_bytes); // Allocates a new block of the given size from stack top
Marker getMarker(); // Returns a Marker to the current stack top
void freeToMarker(Marker marker); // Rolls the stack back to a previous marker
void clear(); // Clears the entire stack(rolls the stack back to zero)
private:
// ...
}
EDIT:
After a while I got this working but I don't know if I'm doing it right
Header File
typedef std::uint32_t U32;
struct Marker {
size_t currentSize;
};
class StackAllocator
{
private:
void* m_buffer; // Buffer of memory
size_t m_currSize = 0;
size_t m_maxSize;
public:
void init(size_t stackSize_bytes); // allocates size of memory
void shutDown();
void* allocUnaligned(U32 size_bytes);
Marker getMarker();
void freeToMarker(Marker marker);
void clear();
};
.cpp File
void StackAllocator::init(size_t stackSize_bytes) {
this->m_buffer = malloc(stackSize_bytes);
this->m_maxSize = stackSize_bytes;
}
void StackAllocator::shutDown() {
this->clear();
free(m_buffer);
m_buffer = nullptr;
}
void* StackAllocator::allocUnaligned(U32 size_bytes) {
assert(m_maxSize - m_currSize >= size_bytes);
m_buffer = static_cast<char*>(m_buffer) + size_bytes;
m_currSize += size_bytes;
return m_buffer;
}
Marker StackAllocator::getMarker() {
Marker marker;
marker.currentSize = m_currSize;
return marker;
}
void StackAllocator::freeToMarker(Marker marker) {
U32 difference = m_currSize - marker.currentSize;
m_currSize -= difference;
m_buffer = static_cast<char*>(m_buffer) - difference;
}
void StackAllocator::clear() {
m_buffer = static_cast<char*>(m_buffer) - m_currSize;
}
Okay for simplicity let's say you're tracking a collection of MyFunClass for your engine. It could be anything, and your linear allocator doesn't necessarily have to track objects of a homogenous type, but often that's how it's done. In general, when using custom allocators you're trying to "shape" your memory allocations to separate static data from dynamic, infrequently accessed vs. frequently accessed, with a view towards optimizing your working set and achieving locality of reference.
Given the code you provided, first, you'd allocate your memory pool. For simplicity, assume you want enough space to pool 1000 objects of type MyFunClass.
StackAllocator sa;
sa.Init( 1000 * sizeof(MyFunClass) );
Then each time you need to "allocate" a new block of memory for a FunClass, you might do it like this:
void* mem = sa.allocUnaligned( sizeof(MyFunClass) );
Of course, this doesn't actually allocate anything. All the allocation already happened in Step 1. It just marks some of your already-allocated memory as in-use.
It also doesn't construct a MyFunClass. Your allocator isn't strongly typed, so the memory it returns can be interpreted however you want: as a stream of bytes; as a backing representation of a C++ class object; etc.
Now, how would you use a buffer allocated in this fashion? One common way is with placement new:
auto myObj = new (mem) MyFunClass();
So now you're constructing your C++ object in the memory space you reserved with the call to allocUnaligned.
(Note that the allocUnaligned bit gives you some insight into why we don't usually write our own custom allocators: because they're hard as heck to get right! We haven't even mentioned alignment issues yet.)
For extra credit, take a look at scope stacks which take the linear allocator approach to the next level.
In the code below, many vectors with each 10 ints gets constructed with 60% chance or an existing vector gets deleted with 40% chance. Thus, there will be many calls to new/malloc and delete.
Since all these vectors are of type vector<int>, can a custom allocator help here to reduce calls to new and delete and thus increase performance? The idea is that the space of a deleted vector can be reused by a newly constructed one. How would such a allocator look like?
Note: This question is about allocators, that reduces calls to new and delete.
#include <iostream>
#include <vector>
#include <random>
using namespace std;
int main()
{
// Random generator and distribution
mt19937 gen(123456);
uniform_real_distribution<> dis01(0., 1.);
// Make or delete 10E6 vectors.
vector< vector<int> > v; //the inner vectors will make many calls to new and delete
v.reserve(10E5); //assume some size.
for(int i=0; i<10E6; ++i)
{
if(dis01(gen)<0.6) // if true: make new sub-vector
{
v.emplace_back(); //new sub-vector
v.back().reserve(10);
for(int k=0; k<10; ++k)
v.back().emplace_back(k); //fill it with some numbers
}
else // else, delete the last entry if there is one.
if(!v.empty())
v.pop_back();
}
cout<<"v.size()= "<<v.size();
return 0;
}
You might be able to gain some performance by writing an allocator to more efficiently reuse recently freed memory, especially if all the vectors are going to be size 10. Of course, if that's the case, you'll gain more performance by using an object of fixed size. If the size of allocation for the vectors needs to be dynamic, then your problem is as abstract as general memory allocation, and you're not likely to improve on the standard allocator.
You are not at all likely to improve performance vs. STL unless you're able to leverage information which applies to your specific case but not the more general case.
A much better solution would be not to delete the vector objects, but just leave them in the vector>, maintain an iterator/pointer to the "end" of the vector (decremented instead of deleting), and then rather than emplacing an element (constructing a vector) you just advance your iterator, test for .end(), and then emplace if needed, otherwise reuse the old vector. This assumes that your program does not rely on side affects of constructor or destructor (vector doesn't, but you're not telling us your actual use case).
As I understand from https://en.wikipedia.org/wiki/Allocator_(C%2B%2B), C++ allocators reduce requests for allocation and deallocation for a specific container. I assume this means creating and deleting containers still require calls to new and delete.
You might want to look at https://github.com/gperftools/gperftools. It is a replacement for malloc. It claims improvements in small object allocation especially in multi-threaded programs.
This is for C++11. Older standards need additional stuff
implemented in the allocator [1].
This is proof-of-concept code. It runs and solves the example
problem but suffers from several limitations. It still
demonstrates how a custom allocator can be used to improve
performance in a scenario where a lot of std::vectors are
created and destroyed.
PoolAlloc.hh:
template<typename T>
struct MemChunk
{
std::size_t buf_size=0;
T* buf=nullptr;
T* top=nullptr;
std::size_t used=0;
};
template<typename T>
class PoolAllocator
{
public:
using value_type=T;
PoolAllocator();
explicit PoolAllocator(std::size_t);
PoolAllocator(PoolAllocator const&) noexcept;
template<typename U>
PoolAllocator(PoolAllocator<U> const&) noexcept;
PoolAllocator(PoolAllocator&&) noexcept;
PoolAllocator& operator=(PoolAllocator const&)=delete;
PoolAllocator& operator=(PoolAllocator&&)=delete;
~PoolAllocator();
template <typename U>
struct rebind
{
using other=PoolAllocator<U>;
};
T* allocate(std::size_t);
void deallocate(T*, std::size_t) noexcept;
template<typename U1, typename U2>
friend bool operator==(PoolAllocator<U1> const&, PoolAllocator<U2> const&) noexcept;
private:
std::vector<MemChunk<T>>* memory_=nullptr;
int* ref_count_=nullptr;
std::size_t default_buf_size_=0;
};
template<typename T>
PoolAllocator<T>::PoolAllocator():
PoolAllocator{100000} {}
template<typename T>
PoolAllocator<T>::PoolAllocator(std::size_t buf_size):
memory_{new std::vector<MemChunk<T>>},
ref_count_{new int(0)},
default_buf_size_{buf_size}
{
memory_->emplace_back();
memory_->back().buf_size=buf_size;
memory_->back().buf=new T[buf_size];
memory_->back().top=memory_->back().buf;
++(*ref_count_);
}
template<typename T>
PoolAllocator<T>::PoolAllocator(PoolAllocator const& src) noexcept:
memory_{src.memory_},
ref_count_{src.ref_count_},
default_buf_size_{src.default_buf_size_}
{
++(*ref_count_);
}
template<typename T>
PoolAllocator<T>::PoolAllocator(PoolAllocator&& src) noexcept:
memory_{src.memory_},
ref_count_{src.ref_count_},
default_buf_size_{src.default_buf_size_}
{
src.memory_=nullptr;
src.ref_count_=nullptr;
}
template<typename T>
template<typename U>
PoolAllocator<T>::PoolAllocator(PoolAllocator<U> const& src) noexcept:
memory_{src.memory_},
ref_count_{src.ref_count_},
default_buf_size_{src.default_buf_size_}
{
++(*ref_count_);
}
template<typename T>
PoolAllocator<T>::~PoolAllocator()
{
if (ref_count_!=nullptr)
{
--(*ref_count_);
if (*ref_count_==0)
{
if (memory_!=nullptr)
{
for (auto& it : *memory_)
{
delete[] it.buf;
}
delete memory_;
}
delete ref_count_;
}
}
}
template<typename T>
T*
PoolAllocator<T>::allocate(std::size_t n)
{
MemChunk<T>* mem_chunk=&memory_->back();
if ((mem_chunk->used+n)>mem_chunk->buf_size)
{
default_buf_size_*=2;
memory_->emplace_back();
mem_chunk=&memory_->back();
std::size_t buf_size=default_buf_size_;
if (n>default_buf_size_)
{
buf_size=n;
}
mem_chunk->buf_size=buf_size;
mem_chunk->buf=new T[mem_chunk->buf_size];
mem_chunk->top=mem_chunk->buf;
}
T* r=mem_chunk->top;
mem_chunk->top+=n;
mem_chunk->used+=n;
return r;
}
template<typename T>
void
PoolAllocator<T>::deallocate(T* addr, std::size_t n) noexcept
{
MemChunk<T>* mem_chunk=&memory_->back();
if (mem_chunk->used>n and (mem_chunk->top-n)==addr)
{
mem_chunk->used-=n;
mem_chunk->top-=n;
}
}
template<typename U1, typename U2>
bool operator==(PoolAllocator<U1> const& lhs, PoolAllocator<U2> const& rhs) noexcept
{
return (std::is_same<U1, U2>::value and lhs.memory_==rhs.memory_);
}
Using your example modified in the following way:
#include <iostream>
#include <vector>
#include <random>
#include "PoolAlloc.hh"
using namespace std;
int main()
{
// Random generator and distribution
mt19937 gen(123456);
uniform_real_distribution<> dis01(0., 1.);
PoolAllocator<int> palloc{1000000};
// Make or delete 10E6 vectors.
vector< vector<int, PoolAllocator<int>> > v; //the inner vectors will make many calls to new and delete
v.reserve(10E5); //assume some size.
for(int i=0; i<10E6; ++i)
{
if(dis01(gen)<0.6) // if true: make new sub-vector
{
v.emplace_back(palloc); //new sub-vector
v.back().reserve(10);
for(int k=0; k<10; ++k)
v.back().emplace_back(k); //fill it with some numbers
}
else // else, delete the last entry if there is one.
if(!v.empty())
v.pop_back();
}
cout<<"v.size()= "<<v.size();
return 0;
}
The number of calls to malloc drops from ~6e6 down to 21. Total
number of instructions drops from 3.7e9 to 2.5e9 (using -O3,
measured with valgrind --tool=callgrind).
There are a couple of implementation details that will impact the
performance in different situations of usage.
Currently multiple buffers are used. If one is full, another one
is created. This way there never has to be a reallocation
operation which would get you into a world of hurt (see
comments).
The biggest question is, how to deal with deallocated memory.
Currently a trivial approach is used that only makes deallocated
memory available to later allocates when it is at the end of the
buffer. For your example that is sufficient, as you only
deallocate memory at the end of the buffer.
For more complex scenarios you will need a more sophisticated
mechanism. Some data structure is needed to store the addresses
and sizes of free memory chunks. Multiple concepts are possible
here and their performance will vary with the concrete situation
they are used in. I doubt there is a good one-size-fits-all
solution here.
[1] http://howardhinnant.github.io/stack_alloc.html
Custom allocators might solve some problems, but it's not a silver bullet. The example is not enough for to know what the best solution would be. I'm going to suggest something different, not because it's better, but because it could be better in some instance.
v.resize(10E5, std::vector<int>(10));
Instead of
v.reserve(10E5);
But then you need an iterator for the next free slot on the vector and all that good stuff.
Have you tried existing boost pool allocator?
http://theboostcpplibraries.com/boost.pool
I think, if it comes to reuse 'std::vector's object itself' memory, it should be somewhat related to inplacement creation/pools.
The way that I see it, custom allocators really only offer a benefit over standard allocators when you know exactly how the memory will be used. In general, you are making a size/perf trade off and the custom allocator is what allows you to control this decision.
If in you example you can use page size chunks for each list then you can just keep a Free list of pages and hand them out on all future allocations. This would have a lot of memory overhead if you really only have ten ints in each list but could be a big win if the lists are larger and could be done with no calls to new or delete per int. This is essentially creating a fixed size memory pool for each list. When you are done with the list you just put the page back in the free list and use it for the next list.
My assignment is to implement a stack (array-based) with a given capacity that, when attempting to add another element after the stack is full, will grow by a constant value (I used 100).
The problem here I believe lies in my push() function, which adds 100 element to the stack... probably syntactical but I'm not sure at all why my program won't execute.
template<class Type>
class ArrayStack{
enum {default_cap=100};
private:
Type* S; //array storing elements
int CAP; //capacity of stack
int TOP; //top element of stack
public:
ArrayStack(int defc = default_cap); //constructor with default parameter
~ArrayStack(){} //is "delete [] S;" supposed to go in here? not sure
bool isEmpty() const { return (TOP<0); }//is the stack empty?
int size() const { return (TOP+1); }
const Type& top(){ return S[TOP];} //has exception handling, not displayed
Type pop() {--TOP;} //removes top element
//here's the function that concerns me:
//--------------------------------------------
void push (const Type& e){
if(size() == CAP) {
Type* Snew = new Type[CAP+100];
for(int i = 0; i < CAP; i++){
Snew[i] = S[i];
}
delete [] S;
++CAP;
S = Snew;
}
S[++TOP] = e;
}
//--------------------------------------------
//other functions...
};
//constructor:
template<typename T> ArrayStack<T>::ArrayStack(int d)
: S(new T[d]), CAP(d), TOP(-1){}
It's a bit hard to comment since you've only provided partial code, and haven't demonstrated usage (e.g. with a main() function).
However, an obvious problem [which I notice Roger Rowland has identified in his comment too] with the push() function is that it increases allocated size by 100, but only increments CAP. So it will add 100 elements to the array, but only report ability to use the first one added.
The pop() function also discards the top element, and doesn't return it. If the caller ever tries to use the return value from pop() - and users of a stack type do normally expect to be able to use values they pop - the result will be undefined behaviour.
Your destructor definitely needs to use operator delete, unless you clean up dynamically allocated memory in some other way (and you've shown nothing like that). The whole point of operator new is that memory is NOT released until a corresponding operator delete. It will not be cleaned up magically if you forget to do it, and will present a memory leak for (at least) as long as your program runs.
If you want to do things a bit more safely, use a std::vector instead of a pointer (and avoid using operator new directly).
My Problem is my delete function is not working.When i pushed element
onto the stack then display it and after that if i delete the whole stack so when i displayed it
it should output garbage values but this is not the case it is showing the same values again as if nothing deleted.
#include<iostream.h>
#include<conio.h>
template<class T>
class stack
{
int capacity;
public:
T *array;
int top;
stack(int);
void push(T);
T pop();
int stackempty();
int stackfull();
void display();
void deletestack();
};
template<class T>
stack<T>::stack(int max)
{
top=-1;
capacity=max;
array=new T[capacity];
}
template<class T>
void stack<T>::push(T data)
{
if(stackfull())
cout<<"stack overflow";
else
array[++top]=data;
}
template<class T>
T stack<T>::pop()
{
if(stackempty())
{
cout<<"stack underflow";
return -1;
}
else
return array[top--];
}
template<class T>
int stack<T>::stackempty()
{
if(top==-1)
return 1;
else
return 0;
}
template<class T>
int stack<T>::stackfull()
{
if(top==capacity-1)
return 1;
else
return 0;
}
template<class T>
void stack<T>::display()
{
int i;
if(top==-1)
cout<<"empty stack";
else
for(i=0;i<=top;i++)
cout<<"\n"<<array[i];
}
template<class T>
void stack<T>::deletestack()
{
delete []array;
}
int main()
{
stack<int>* S=new stack<int>(20);
clrscr();
S->push(5);
S->push(10);
S->push(15);
S->display();
S->deletestack();
S->display();
S->push(5);
S->display();
getch();
return 0;
}
That's a lot more code than necessary to illustrate this problem, but anyway... If you delete[] an array, it is Undefined Behaviour to access it again. By accessing it again, your program could do anything at all.
That includes displaying the values that were already in this memory. These are garbage. They just happen to be garbage that looks a lot like the values your already put here. The reason this happens is that it would be extra work to go through and fill this memory with, say, zeroes. You're not allowed to look at it any more, so who cares what is there.
Imagine you owned some houses in a colony. Now you don't own them. This doesn't mean that those houses are destroyed. It may happen that the houses are there as it is. It may happen there is no house there.
Similarly, the memory alloted for your array is still there and you have its address. It may happen that location has same data or not.
The line delete []array; in your method deletestack() will free the array of memory that the pointer called array is pointing at. (however free is not the same as write over it with something else)
After you call delete you return the memory to the operating system, accessing it after it was returned to the operating system is undefined behavior. Sometimes it will cause a segmentation fault and a crash and sometimes nothing bad will happen. C++ in this respect is very different from languages like Java that actively look for illegal memory access and immediately throw an exception. In C++ the programmer is expected to follow the rules and no one is checking after him.
In any case freeing the memory does not mean that it is cleared. The old values you placed in there are still there until something else will get this memory allocated and start writing to it.
There are many other issues in the code you posted that will cause weird behavior if it is used:
stack<int>* S=new stack<int>(20); allocated an object on the heap that is never deleted.
the class stack allocates an array during construction however the dtor does not release the array. A user who forgets to call deletestack will cause memory to leak
the class stack holds a reference to an allocated array however it does not have a copy-ctor or a operator= and it does not delete them.
I'm new to this website and will try to contribute just as much as I ask. Also, please know I never ask a question without spending much time trying to figure it out myself.
As such, C++ Stacks are driving me f***ing crazy.
My Question: where do I place my variables/values in the Stack function block to actually use it. I understand Stacks are a LIFO data-structure, I've read countless examples of stacking plates on top of each other, etc.
Look at this code:
#include <iostream>
using namespace std;
const int MAX_SIZE = 100;
class StackOverFlowException
{
public:
StackOverFlowException()
{
cout << "Stack overflow" << endl;
}
};
class StackUnderFlowException
{
public:
StackUnderFlowException()
{
cout << "Stack underflow" << endl;
}
};
class ArrayStack
{
private:
int data[MAX_SIZE];
int top;
public:
ArrayStack()
{
top = -1;
}
void Push(int element)
{
if ( top >= MAX_SIZE )
{
throw new StackOverFlowException();
}
data[++top] = element;
}
int Pop()
{
if ( top == -1 )
{
throw new StackUnderFlowException();
}
return data[top--];
}
int Top()
{
return data[top];
}
int Size()
{
return top + 1;
}
bool isEmpty()
{
return ( top == -1 ) ? true : false;
}
};
[etc....]
It's basic cookie-cutter....let's say I'm trying to adapt it to express a system where the last food orders placed in, are kicked out first; variables are 'food', 'orders', and whatever else.
Where in the world am I integrating those variables into that stack code above!?!??!
Please help so confused i'm about to indiscriminately punch something
A stack implementation could use templates so that you could put whatever you want in the stack (within reason).
For example, have a class that encapsulates all the data related to orders (this one is just an example):
class FoodOrder
{
int orderNumber;
time_t orderTime;
// add more variables here
}
Then, your stack could look like this:
template<typename T> class Stack
{
T data[MAX_SIZE];
int top;
void Push(T item);
T Pop(void);
// add methods
}
Then, you could have a Stack of whatever items you want:
Stack<int> stackOfInts;
Stack<std::string> stackOfStrings;
Stack<FoodOrder> stackOfOrders;
Use the existing std::stack and wrap it if you want the exceptions, for example like this (note you could easily templatize it):
class protectedstack
{
private:
std::stack<int> stack;
const int myarbitraryupperlimit = 100;
public:
void pop()
{
if(stack.empty())
{
throw new StackUnderFlowException();
}
stack.pop();
}
void push(const int& value)
{
if(stack.size()>=myarbitraryupperlimit)
{
throw new StackOverFlowException();
}
stack.push(value);
}
// Similar for top/empty/constructors/...
};
The type of data, as well as that of what Top & Pop return, and what Push takes as an argument, is what is contained in the stack; that's what you'd replace w/ the type of whatever you want to make this a stack of.
This is a stack:
Think of it this way: the only way to add a book without moving the others is to place it on top : this is what Push does. So by calling Push(Book1), you'd be placing Book1 on top of the pile.
Similarly, the only way to take away a book without moving the others is to take the one on top : this is what Pop does. So by calling Pop(), you'd be getting (and removing from the stack) whichever book is on top of the stack, which in the image is the green book.
Am I missing something or was this your question?
It's all in the top variable. This variable dictates which object is the current top. When you pop(), then the top variable is reduced- meaning that the top is now one below where it was. When you push(), it's incremented- the top is now one above where it was. This variable is what accounts for the LIFO functionality of the stack.
You can, of course, template the class to make it work with a FoodOrder or whatever.
I don't see why the confusion. The data would go in, duh!, the "data" variable.
So, you either use Templates, to make the data buffer able to hold anything, or you change the type of the data to what you specifically need.
If for example you have a FoodOrder class, you can do it like this (my C++ is rusty, but this is basically the gist of it):
FoodOrder *data[MAX_SIZE];
You would have to change the push/pop parameters to accept a FoodOrder pointer/reference accordingly, and you're set.
P.S. About using std::stack --this might be a better solution, but doesn't answer his specific question.
P.S 2 Poster writes: "I'm new to this website and will try to contribute just as much as I ask.". Really? So why hasn't he even picked an answer yet? Does this smell more like a homework assignment?