The only issues I can see is that your code won't function properly for scenarios where head or tail are updated to NULL.
Basically if you delete the only node, dHead will point to null, so you need to put "guards" around subsequent statements like
dHead->prev = NULL;
like so
if (dHead != NULL) {
dHead->prev = NULL;
}
One way to "get around" having so many conditionals is to assign a NIL (not a type) element.
NIL is the "node" which replaces NULL. It represents "off the data portion of the list" so its next is ALWAYS NIL (a circular reference) and it's previous is ALWAYS NIL (a circular reference). In such cases, you ensure that NIL is never accessible outside of the list, and that head->prev == NIL and tail->next == NIL. That way you can avoid the many if (tail != null) { ... }
type statements.
Under such a construct, an empty list is one where head == NIL && tail == NIL
. This dramatically reduces the number of if (something == null)
statements, but you still have one if statement to consider, when head is NIL (after deleting something) you need to set tail to NIL for consistency's sake.