简体   繁体   中英

C++| BST reference-to-node-pointer vs. a node-pointer

Suppose I have this BST template:

template <typename T> class Node {
private:
public:
  T data;
  Node *left;
  Node *right;

  Node(T dt) : data{dt}, left{nullptr}, right{nullptr} {}
  ~Node() {
    this->data = 0;
    this->left = nullptr;
    this->right = nullptr;
  }
};

template <typename T> class BST {
private:
  Node<T> *_root;

  _insert();
  _add();
  _printOrder_In(Node<T> *parent, std::ostream& os) {
    if (!parent) return;
    _printOrder_In(parent->left, os);
    os << parent->data << ' ';
    _printOrder_In(parent->right, os);
  }

public:
  BST() : _root{nullptr} {}
  ~BST();
  
  insert();
  add();
  std::ostream& print(std::ostream& os = std::cout) {
    _printOder_In(this->_root, os);
    return os;
  }
};

Why do the following code work when I pass a reference-to-the-node-pointer, not work when I pass the-node-pointer?

// BST MEMBER FUNCTIONS:
private:
  void _insert(Node<T>* &parent, const T &val) { // works
//void _insert(Node<T>*  parent, const T &val) { // doesn't work, apparently generates nodes indefinitely
    if (!parent)
      parent = new Node<T>{val};
    else {
      if (val < parent->data)
        _insert(parent->left, val);
      else if (val > parent->data)
        _insert(parent->right, val);
      else
        return;
    }
  }

public:
  void insert(const T &val) {
    _insert(this->_root, val);
  }
};

Also as opposed to this alternate method, which simply works with the passed pointer:

// BST MEMBER FUNCTIONS:
private:
  void _add(Node<T>* parent, T val) {
    if (parent->data > val) {
      if (!parent->left) {
        parent->left = new Node<T>{val};
      } else {
        _add(parent->left, val);
      }
    } else {
      if (!parent->right) {
        parent->right = new Node<T>{val};
      } else {
        _add(parent->right, val);
      }
    }
  }

public:
  void add(T val) {
    if (this->_root) {
      this->_add(this->_root, val);
    } else {
      this->_root = new Node<T>(val);
    }
  }

I understand that a reference-to-the-point will give me direct access to the passed pointer. However, I am stuck at the difference between the two methods. In the second method, the local copy used in the controlflow still works despite the pointer itself not being passed as reference.

OPs issue is about call-by-value vs. call-by-reference .

The language C (the “anchestor” of C++) provides exclusively call-by-value. The missing call-by-reference can be mimed by using the address of a variable instead of the variable itself. (Of course, the parameter of the resp. function has to become a pointer-to-type instead of the type itself.)

So, a pointer is passed-by-value but its value can be used to access something outside the scope of the function, and the modification (done at its original storage) will survive the return from that function.

When C++ evolved out of C, this principle has been taken over. However, C++ added the call-by-reference like it is known from other comparable languages (eg Pascal).

A simple demonstration of call-by-value vs. call-by-reference:

#include <iostream>

void callByValue(int a)
{
  std::cout
    << "callByValue():\n"
    << "  a: " << a << '\n'
    << "  a = 123;\n";
  a = 123;
  std::cout
    << "  a: " << a << '\n';
}

void callByRef(int &a)
{
  std::cout
    << "callByRef():\n"
    << "  a: " << a << '\n'
    << "  a = 123;\n";
  a = 123;
  std::cout
    << "  a: " << a << '\n';
}

int main()
{
  int b = 0;
  std::cout << "b: " << b << '\n';
  callByValue(b);
  std::cout << "b: " << b << '\n';
  callByRef(b);
  std::cout << "b: " << b << '\n';
}

Output:

b: 0
callByValue():
  a: 0
  a = 123;
  a: 123
b: 0
callByRef():
  a: 0
  a = 123;
  a: 123
b: 123

Explanation:

  • The change of a has a local effect only in callByValue() because a is passed by value. (Ie a copy of the argument is passed to the function.)
  • The change of a modifies the passed argument in callByRef() because a is passed by reference.

Easy-peasy? Of course. But that's exactly the same if parameter type int of a is replaced by any other type – eg Node* or even Node<T>* .

I took out the relevant lines from OPs code:

  void _insert(Node<T>* &parent, const T &val) { // works
    if (!parent)
      parent = new Node<T>{val};

If the value of argument parent is a nullptr then the parent is assigned the address of a new created Node<T> . Thereby, the pointer (variable) passed by reference is modified. Hence, the modification persists after leaving the function _insert() .

The other alternative:

  void _insert(Node<T>*  parent, const T &val) { // doesn't work, apparently generates nodes indefinitely
    if (!parent)
      parent = new Node<T>{val};

If the value of argument parent is a nullptr then the parent is assigned the address of a new created Node<T> . Thereby, the pointer is passed by value. So, the (original) variable (which was used in the call) is not changed – and still contains the nullptr when the function is left.

Btw. According to this, the address of the created Node<T> gets lost. (It's not stored anymore anywhere.) However, the Node<T> instance still resides in their allocated memory – inaccessible until end of process – degraded to a piece of wasted memory. This is an example how memory-leaks may occur.

Please, don't confuse this fact with the other that the pointer itself “mimes” a pass-by-reference. Modifications of the object (of type Node<T> ) the pointer points to (if it's not nullptr ) will become persistent.

Having a closer look onto _add() it appears that only the pointed object (of type Node<T> ) is modified but never the pointer itself. So, passing it by value is completely sufficient and fine.

But for the correct working of _insert() , modifications of parent itself have to become persistent as well. Thus, only the first alternative works correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM