If 'A' has a member variable 'x' of type 'A*', and 'B' inherits from 'A', how to redefine 'x' as 'B*' inside of 'B'?

有些话、适合烂在心里 提交于 2021-01-29 13:37:14

问题


Edit: a simpler version of the question:

class ForwardNode // A
{
public:

    char val;
    ForwardNode* next; // x

    ForwardNode(char val, ForwardNode* next = nullptr) : val(val), next(next) {}
};

class BidirectionalNode : public ForwardNode // B
{
public:

    BidirectionalNode* prev;

    BidirectionalNode(char val, BidirectionalNode* next = nullptr, BidirectionalNode* prev = nullptr)
        : ForwardNode(val, next), prev(prev) {}
};

Here I'm defining a forward node and bidirectional node. Forward node have reference to the next node only. Bidirectional node has a reference to both previous and next node.

It looks like BidirectionalNode is exactly the same as ForwardNode but with the extra reference to the previous node. So I let BidirectionalNode inheret from ForwardNode. But now each time I have to treat next or current as a BidirectionalNode, I'll have to cast it first since it's defined as ForwardNode.

Here is an example:

class ForwardList
{
public:

    ForwardNode* head;
    ForwardNode* tail;
    ForwardNode* current = nullptr;

    ForwardList(ForwardNode* head, ForwardNode* tail) :
        head(head), tail(tail) {}

    void PrintListForwards()
    {
        current = head;
        do
        {
            std::cout << current->val;
            current = current->next;
        } 
        while (current != nullptr);

        std::cout << std::endl;
    }
};

// BidirectionalList is a ForwardList for the same reason 
// BidirectionalNode is a ForwardNode.
class BidirectionalList : public ForwardList
{
public:

    BidirectionalList(BidirectionalNode* head, BidirectionalNode* tail) :
        ForwardList(head, tail) {}

    void PrintListBackwards()
    {
        current = tail;
        do
        {
            std::cout << current->val;

            // here I wanna redefine current as BidirectionalNode*
            // instead of casting every time they are used.
            current = ((BidirectionalNode*)current)->prev;
        } 
        while (current != nullptr);

        std::cout << std::endl;
    }
};

Here I have to cast current to BidirectionalNode* to be able to use its prev.

Is there a way to redefine next and current as BidirectionalNode* instead of casting it each time?

Another example:

std::pair<BidirectionalNode*, BidirectionalNode*> CreateBidirectionalList(std::string string)
{
    if (string.empty())
    {
        BidirectionalNode* node = new BidirectionalNode{ 0 };
        return { node, node };
    }

    BidirectionalNode* head = new BidirectionalNode{ string[0] };
    BidirectionalNode* tail = head;
    for (int i = 1; i < string.size(); i++)
    {
        tail->next = new BidirectionalNode{ string[i] };

        // have to cast.
        ((BidirectionalNode*)(tail->next))->prev = tail;
        // have to cast again.
        tail = ((BidirectionalNode*)tail->next);

        tail->val = string[i];
    }

    return { head, tail };
}

Original Question:

#include <iostream>

class Node
{
public:

    int value;
    Node* next;

    Node(Node* next, int value) : next(next), value(value) {}
};

class DoublyLinkedNode
{
public:

    int value;
    DoublyLinkedNode* prev;
    DoublyLinkedNode* next;

    DoublyLinkedNode(DoublyLinkedNode* prev, DoublyLinkedNode* next, int value)
        : prev(prev), next(next), value(value) {}
};

class ILinkedList
{
public:

    virtual void PushBack(int value) = 0;
    virtual void SetCurrentToBegin() = 0;
    virtual int GetCurrent() = 0;
    virtual bool HasNext() = 0;
    virtual void SetToNextNode() = 0;
};

class IDoublyLinkedList : public ILinkedList
{
public:

    virtual void PushFront(int value) = 0;
    virtual void SetCurrentToEnd() = 0;
    virtual bool HasPrev() = 0;
    virtual void SetToPrevNode() = 0;
};

class LinkedList : public ILinkedList
{
protected:

    Node* head;
    Node* tail;
    Node* current;

public:

    LinkedList()
    {
        std::cout << "Linked List Created." << std::endl;

        tail = new Node{ nullptr, 0 };
        head = new Node{ tail, 0 };
        current = head;
    }

    virtual void PushBack(int value) override
    {
        tail->value = value;
        tail->next = new Node{ nullptr, 0 };
        tail = tail->next;
    }

    virtual int GetCurrent() override
    {
        return current->value;
    }

    virtual void SetCurrentToBegin() override
    {
        current = head->next;
    }

    virtual bool HasNext() override
    {
        return current != tail;
    }

    virtual void SetToNextNode() override
    {
        if (current == tail)
            SetCurrentToBegin();
        else
            current = current->next;
    }
};

class DoublyLinkedList : public IDoublyLinkedList
{

    int value;
    DoublyLinkedNode* head;
    DoublyLinkedNode* tail;
    DoublyLinkedNode* current;

public:

    DoublyLinkedList()
    {
        std::cout << "Doubly Linked List Created." << std::endl;

        head = new DoublyLinkedNode{ nullptr, nullptr, 0 };
        tail = new DoublyLinkedNode{ nullptr, nullptr, 0 };
        head->next = tail;
        tail->prev = head;
        current = head;
    }

    virtual void PushBack(int value) override
    {
        tail->value = value;
        tail->next = new DoublyLinkedNode{ tail, nullptr, 0 };
        tail = tail->next;
    }

    virtual void PushFront(int value) override
    {
        head->value = value;
        head->prev = new DoublyLinkedNode{ nullptr, head, 0 };
        head = head->prev;
    }

    virtual void SetCurrentToEnd() override
    {
        current = tail->prev;
    }

    virtual bool HasPrev() override
    {
        return current != head;
    }

    virtual void SetToPrevNode() override
    {
        if (current == head)
            SetCurrentToEnd();
        else
            current = current->prev;
    }

    virtual int GetCurrent() override
    {
        return current->value;
    }

    virtual void SetCurrentToBegin() override
    {
        current = head->next;
    }

    virtual bool HasNext() override
    {
        return current != tail;
    }

    virtual void SetToNextNode() override
    {
        if (current == tail)
            SetCurrentToBegin();
        else
            current = current->next;
    }
};

void PrintForward(ILinkedList& list)
{
    list.SetCurrentToBegin();

    while (list.HasNext())
    {
        std::cout << list.GetCurrent() << ' ';
        list.SetToNextNode();
    }

    std::cout << std::endl;
}

void PrintBackward(DoublyLinkedList& list)
{
    list.SetCurrentToEnd();

    while (list.HasPrev())
    {
        std::cout << list.GetCurrent() << ' ';
        list.SetToPrevNode();
    }

    std::cout << std::endl;
}

int main()
{
    LinkedList ll;

    for (int i = 1; i <= 5; i++)
        ll.PushBack(i);

    PrintForward(ll);

    std::cout << std::endl;

    // -----------------------------------

    DoublyLinkedList dll;

    for (int i = 1; i <= 5; i++)
        dll.PushFront(i), dll.PushBack(i);

    PrintForward(dll);
    PrintBackward(dll);
}

Output:

Linked List Created.
1 2 3 4 5

Doubly Linked List Created.
5 4 3 2 1 1 2 3 4 5
5 4 3 2 1 1 2 3 4 5

Here is a simple implementation of signly linked list and doubly linked list. Here I'm using an interface called ILinkedList and another interface called IDoublyLinkedList that extends ILinkedList. It's obvious that doubly linked list is a singly linked list plus some extra functionality and can be used whenever singly linked list is needed. But because the type of nodes used inside each of them is different, I can't assume that doubly linked list is a singly linked list. In other words, I have to define the exact same functions (just copy and paste without any modification) that are inside the singly linked list just because the type of the nodes used is different. If I let DoublyLinkedNode inherit from Node and just add the extra pointer (prev), and defined the nodes inside the interface ILinkedList, if I define the type of the nodes to be Node, inside DoublyLinkedList, when ever I want to treat my nodes as DoublyLinkedNode I'll have to cast my nodes to DoublyLinkedNode*. If I define them as DoublyLinkedNode, then I'll have to let the prev pointer empty all the time inside LinkedList. A solution (that I don't know how to achieve) is to let DoublyLinkedNode inherit from Node and let DoublyLinkedList inherit from LinkedList, define head, tail, current inside LinkedList to be of type Node* and redifine them as a DoublyLinkedNode* inside DoublyLinkedList. This way I don't have to repeat the exact same code inside LinkedList since DoublyLinkedNode is a child of Node and all operations that can be done on Node can also be done on DoublyLinkedNode anyways. Also redefine next inside DoublyLinkedNode to be of type DoublyLinkedNode*.


回答1:


A linked list is a collection of nodes such that each node refers to the next node. The definition of a linked list does not need to fix what a "node" means: different node types still admit a linked list structure. Reflect this fact in your code: template LinkedList over the node type. Note that this template class loses the ability to create nodes: its constructor cannot create the initial list and it cannot have a PushBack. This means that your code now has three classes: a base LinkedList containing "read-only", common operations, and then Singly- and Doubly- linked subclasses.

struct IDoublyLinkedList : virtual ILinkedList // virtual to avoid multiple inheritance diamond problems!
{ /* etc */ };

template<typename N>
class LinkedList : public virtual ILinkedList
{
protected:
    N *head, *tail, *current;
    LinkedList(N *head, N *tail)
        : head(head), tail(tail), current(head)
    {
        std::cout << "Linked List Created.\n"; // std::endl is usually unnecessary; \n is portably handled anyway
        head->next = tail;
    }

public:    
    virtual int GetCurrent() override
    {
        return current->value;
    }

    virtual void SetCurrentToBegin() override
    {
        current = head->next;
    }

    virtual bool HasNext() override
    {
        return current != tail;
    }

    virtual void SetToNextNode() override
    {
        if (current == tail)
            SetCurrentToBegin();
        else
            current = current->next;
    }
};

struct SinglyLinkedList : LinkedList<Node>
{
    SinglyLinkedList() : LinkedList(new Node(nullptr, 0), new Node(nullptr, 0))
    {
        std::cout << "Singly Linked List Created.\n";
    }

    virtual void PushBack(int value) override
    {
        tail->value = value;
        tail->next = new Node{ nullptr, 0 };
        tail = tail->next;
    }
};

struct DoublyLinkedList : LinkedList<DoublyLinkedNode>, virtual IDoublyLinkedList
{
    DoublyLinkedList() : LinkedList(new DoublyLinkedNode(nullptr, nullptr, 0), new DoublyLinkedNode(nullptr, nullptr, 0))
    {
        std::cout << "Doubly Linked List Created." << std::endl;
        tail->prev = head;
    }

    virtual void PushBack(int value) override
    {
        tail->value = value;
        tail->next = new DoublyLinkedNode{ tail, nullptr, 0 };
        tail = tail->next;
    }

    // doubly-linked list specific methods...
};

This is by no means perfect. If you continue trying to factor things out of these classes into the common class, I think you'd basically converge to the STL's use of allocators and functors.

Full Godbolt example



来源:https://stackoverflow.com/questions/61244089/if-a-has-a-member-variable-x-of-type-a-and-b-inherits-from-a-how-to

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!