lundi 6 janvier 2020

Call a derived class function not in base class without dynamic casting to maximize performance

I am implementing an AVLTree in C++ as an exercise as preparation for future projects. An AVLTree is basically a BSTTree but with the extra detail that the tree is balanced, i.e., for a node x with left child y and right child z, the number of children on the left of x ( child y and children of y) cannot differ from the number of children on the right of x by more than 1 node.

Each node will represent an int value, and has a reference to the parent node, to a possibly existing left child node and to a possibly existing right child node.

class BSTNode {
protected:
    int value;
    BSTNode* parent;
    BSTNode* left;
    BSTNode* right;
    ...
public:
    virtual void setLeftChild(BSTNode* left); 
    ...
}

To track the number of children of a node, i have AVLNode extending BSTNode with two integers, leftAffinity and rightAffinity, where leftAffinity tells me the number of nodes on the left (similarly for the right with rightAffinity). Since i do not ensure that values i'm adding are always unique, i cannot update affinities before finding a spot to place the new node.

class AVLNode : public BSTNode {
private:
    int leftAffinity;
    int rightAffinity;
    ...
public:
    void setLeftChild(BSTNode* left) override;
    ...
}

Once i successfully set the left child of a node to left, in AVLNode i also update leftAffinity of the current node (and recursively to parent of parent until the root of the tree is reached) to left->getLeftAffinity() + left->getRightAffinity() .

The problem here is that functions on affinity are defined in AVLNode, thus i cannot immediately call left->getLeftAffinity() without a cast, as here i don't know whether left is a BSTNode or an AVLNode. I know that the idea is for any child of an AVLNode to also only be an AVLNode, which could be enforced by ensuring that any BSTNode that is not an AVLNode is transformed into an AVLNode.

  • I do not want to change the function argument to receive AVLNode* instead as this forces me to declare left,right and parent as AVLNode* in the AVLNode class, and thus i get duplicate variables, one of each for BSTNode class and one of each for AVLNode class, even if left,right and parent are private in BSTNode class.
  • I do not want to use dynamic casting, as the point of the exercise was to create an efficient tree data structure with O(log n) complexity on insert and get operations. I've read dynamic casting is both expensive and should also be avoided as it usually hints at a design flaw.

Possible approaches:

  • Create a "no-op" function in the BSTNode class that is overriden in AVLNode class to manage node affinities. This function should not be there as it is not related to BSTNode.
  • Dynamic casting on each add operation, and when succesfull, once for each parent until the root of the tree, which is too expensive.
  • Not use virtual at all for these functions, which would force me to overload many functions in AVLNode.
  • Change all functions to receive AVLNode* instead of BSTNode*, while forcing conversion of BSTNode* into AVLNode* with a constructor in AVLNode, but it this would require dynamic casting as otherwise i would lose leftAffinity and rightAffinity of a node being added, and cause other problems with function inheritance.

The choice that looks best to me is to take the "no-op" approach as it would work if performance is key.

I'm new to C++ so i appreciate any thought on the approaches i've written and for example if there is a trivial solution i overlooked. What would be the best choice in this situation?

Aucun commentaire:

Enregistrer un commentaire