1

Given the following code:

template<class S, class T>
class node {
public:
    S key;
    T value;
    node<S, T> *right_node, *left_node, *parent_node, *nearest_right_node, *nearest_left_node;
    int height;

public:
    template<typename, typename> friend
    class avl_tree;

    node() = default;

    node(const S *key, const T *value, node<S, T> *right_node = nullptr, node<S, T> *left_node = nullptr,
         node<S, T> *parent_node = nullptr, node<S, T> *nearest_right_node = nullptr,
         node<S, T> *nearest_left_node = nullptr) : key(*key),
                                                    value(*value),
                                                    right_node(right_node),
                                                    left_node(left_node),
                                                    parent_node(parent_node),
                                                    nearest_right_node(nearest_right_node),
                                                    nearest_left_node(nearest_left_node),
                                                    height(0) {
    }

    ~node() = default;
};

Can I replace the pointers in the c'tor for value and key with references and still able to give key and value a value of nullptr? (for example if S is int*)?

Update:

int* x;

    void update_x(int &new_x)
    {
        x=new_x;
    }

can I call update_x(null_ptr)

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 4
    The `key` and `value` pointers you're passing into your constructor right now *already* can't be null. If they are, you'll dereference them anyways and get Undefined Behavior. – scohe001 Dec 03 '20 at 14:20
  • 2
    Do you know the difference between a reference and a pointer? This is not an obscure corner of the language, its meat-and-potatoes. I think you could benefit from a [good C++ book](https://stackoverflow.com/a/388282/4641116). – Eljay Dec 03 '20 at 14:22
  • 2
    `key(*key)` This is simply always wrong, no ifs or buts. – n. m. could be an AI Dec 03 '20 at 15:33
  • The update is simply invalid. It will not compile. You cannot use it in any way, shape or form. – n. m. could be an AI Dec 03 '20 at 15:45
  • @n.'pronouns'm. why do you say it is always wrong? Unless the paramter `key` is a `nullptr` it is ok-ish. "wrong" in the sense that the code would not pass a review I do agree – 463035818_is_not_an_ai Dec 03 '20 at 16:47
  • Questions should not be edited so drastically that they invalidate existing answers. If need be, post a new question – Mooing Duck Dec 03 '20 at 19:03
  • @largest_prime_is_463035818 It is wrong because it is a wrong tool for the job. The function says it accepts a pointer. Why? It accepts a pointer because it doesn't know any better, not because it needs a pointer. Thus it lies (inadvertingly) about what it needs. – n. m. could be an AI Dec 03 '20 at 20:12
  • @n.'pronouns'm. ok, we are on the same side actually. I got nitpicked recently quite stubbornly for calling something "wrong" that was neither a compiler nor a runtime error, maybe I am still a bit affected by that ;) – 463035818_is_not_an_ai Dec 03 '20 at 20:15

2 Answers2

2

Your node class has members S key and T value, and constructor parameters const S *key and const T *value which initialize the members using key(*key) and value(*value), respectively, which will copy the input values into the members.

If a nullptr is passed in to either parameter, the code has undefined behavior when dereferencing the nullptr.

There is no reason for the parameters to be pointers at all. They should be passed in either by value:

template<class S, class T>
class node {
public:
    S key;
    T value;
    ...

public:
    ...

    node(S key, T value, ...) : key(key), value(value), ... { }
    // or: node(S key, T value, ...) : key(std::move(key)), value(std::move(value)), ... { }

    ...
};

Or by const reference:

template<class S, class T>
class node {
public:
    S key;
    T value;
    ...

public:
    ...

    node(const S& key, const T& value, ...) : key(key), value(value), ... { }

    ...
};

Or by rvalue reference:

template<class S, class T>
class node {
public:
    S key;
    T value;
    ...

public:
    ...

    node(S&& key, T&& value, ...) : key(std::move(key)), value(std::move(value)), ... { }

    ...
};

If S or T are defined as pointer types, then a nullptr can be passed in as the value to be assigned to the key/value members, eg:

node<int, type*> *n = new node<int, type*>(1, nullptr);
type t;
node<int, type*> *n = new node<int, type*>(1, &t);

But if S and T are not pointer types, they should not be passed in as pointers:

type t;
node<int, type> *n = new node<int, type>(1, t);
type t;
node<int, type> *n = new node<int, type>(1, std::move(t));
node<int, type> *n = new node<int, type>(1, type{});
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

The issue in your code boils down to:

void foo(int* x) {
   int y = *x;
}

Passing a nullptr to foo will cause undefined behavior because you shall not dereference a nullptr.

Using a reference is possible but will not change that fact:

void bar(int*& x) {
    int y = *x;
}

This is passing the pointer by reference.

int a;
int* p = &a;
bar(p);

Now x in bar is a reference to the pointer to a. However, you cannot pass a nullpointer to bar, because bar is dereferencing the pointer. Some malicious code could do this:

int* p = nullptr;
bar(p);           // DO NOT DO THIS !!!

And then int y = *x; will invoke undefined behavior.

If the function expect to always get a valid object it should take it by reference:

void foo_bar(int& x) {
    int y = x;   // no danger, all fine
}

References cannot refer to nothing. They must be initialzed:

int& x; // ERROR !
int y;
int& z = y;  // OK, z refers to y

Pointers on the other hand not always point to a valid pointee:

int* p;       // not initialized, no (big) problem
p = nullptr;  // still not pointing to an int
int q;
p = &q;       // now p points to an int

References cannot refer to nothing, thats why when "nothing" is not a valid parameter you should prefer references over pointers. In your case you always require *value to be valid, hence value always must point to a S and you should use a reference instead to avoid the above mentioned problem.


Concerning your update:

int* x;

    void update_x(int &new_x)
    {
        x=new_x;
    }

can I call update_x(null_ptr)

No. You cannot. nullptr is not an int. A reference to an int always references an int.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thanks, but that is not what I mean I will explain –  Dec 03 '20 at 14:38
  • Then why in my last question you and another guy told me to change c'tor pointers to references? that doesn't make sense if key can be null –  Dec 03 '20 at 14:46
  • @whiteforce that is actually in the answer. Look at `bar(int*& x)`. In principle you could pass it a `nullptr` then `x` is a reference to a `nullptr`, but it will blow up as soon as you dereference it (in the line `int y = *x;` (and thats basically what happens in your constructor) – 463035818_is_not_an_ai Dec 03 '20 at 14:49
  • Actually it is the rvalue-to-lvalue conversion on a null reference that is illegal. Creation of a null reference (by dereferencing a null pointer) is not undefined behavior as long as it remains an unused lvalue. – Ben Voigt Dec 03 '20 at 16:12
  • @largest_prime_is_463035818 in your answer, `void bar(int*& x) ... int a; bar(&a);` doesn't compile, as the reference can't be bound: "*error: cannot bind non-const lvalue reference of type ‘int*&’ to an rvalue of type ‘int*’*" It would have to be more like this instead: `int a; int *b = &a; bar(b);` – Remy Lebeau Dec 03 '20 at 16:35
  • @RemyLebeau thanks. Usually I do test my code in answer, but I was a bit sloppy here. Fixed now (i hope) – 463035818_is_not_an_ai Dec 03 '20 at 16:44