Since you are using a container, there's really no reason to expose the ListNode class to your users, especially since all it does is hold a value. Instead, you can modify your addFirst and addLast functions to take the value directly and create a ListNode internally:
void addFirst(int val)
{
ListNode node = new ListNode(val);
if (isEmpty())
{
*head = node;
*tail = node;
size = 1;
}
else {
node.setNext(*head);
head->setPrev(node);
*head = node;
size++;
}
}
Now, if later on you decide to change the way ListNode works, you can change it internally without having to change any code. It's also easier on the user:
LinkedList myList;
myList.addFirst(5);
Lastly, it means that you control how the ListNode objects are actually created, so you can easily control how they are deleted. By leaving their creation to the user, the user might accidentally delete a reference to one and leave your list with a dangling reference. For example, if the user wrote a function like:
LinkedList* createList(std::vector<int> values)
{
LinkedList* myList = new LinkedList;
for (int i = 0; i < values.size(); ++i)
{
ListNode node(values[i]);
myList -> addLast(node);
}
return myList;
}
All of the node objects were declared in the for scope, so once that exits they become invalid. The user might pick up on the problem if they notice addLast takes a reference, or if your documentation explicitly states that the ListNode objects need to be kept valid, but usually container classes take that burden off the of the user.