Furat Mostafa Smadi
Furat Mostafa Smadi

Reputation: 39

What is the use case and the difference between "nullptr" and "NULL"

Hi guys I just watch a tutorial about MOVE CONSTRUCTOR (better than deep copy ) and I don't really understand the concepts I'm a beginner, not a pro so I need your help to understand let's start: first here is the code from the tutorial :

#include <iostream>
#include <vector>

using namespace std;

class Move {
private:
    int *data;
public:
    void set_data_value(int d) { *data = d; }
    int get_data_value() { return *data; }
    // Constructor
    Move(int d);
    // Copy Constructor
    Move(const Move &source);
    // Move Constructor
    Move(Move &&source) noexcept;
    // Destructor
    ~Move();
};

 Move::Move(int d)  {
    data = new int;
    *data = d;
    cout << "Constructor for: " << d << endl;
}

// Copy ctor
Move::Move(const Move &source)
    : Move {*source.data} {
        cout << "Copy constructor  - deep copy for: " << *data << endl;
}

//Move ctor


>  **Move::Move(Move &&source) noexcept 
>         : data {source.data} {
>             source.data = nullptr;
>             cout << "Move constructor - moving resource: " << *data << endl;
>     }**

OK HERE IS THE THING the instructor says "we steal the data and null the pointer so my question waht happend when we assighn our pointer to nullptre is it equal to zero or cant we reach it any more or what ???"

Move::~Move() {
    if (data != nullptr) {
        cout << "Destructor freeing data for: " << *data << endl;
    } else {
        cout << "Destructor freeing data for nullptr" << endl;
    }
    delete data;
}

int main() {
    vector<Move> vec;

    vec.push_back(Move{10});

    vec.push_back(Move{20});
    vec.push_back(Move{30});
    vec.push_back(Move{40});
     vec.push_back(Move{50});
    vec.push_back(Move{60});
    vec.push_back(Move{70});
    vec.push_back(Move{80});


    return 0;
}
  

Upvotes: 3

Views: 2386

Answers (2)

Ranoiaetep
Ranoiaetep

Reputation: 6667

A really nice book I was recommended recently: Effective Modern C++ by Scott Meyers.

Neither 0 nor NULL has a pointer type.


Consider below code:

    void f(int);     // three overloads of f
    void f(bool);
    void f(void*);

    f(0);            // calls f(int), not f(void*)
    f(NULL);         // might not compile, but typically calls
                     // f(int). Never calls f(void*)

nullptr’s actual type is std::nullptr_t

    f(nullptr);      // calls f(void*) overload`

It can also improve code clarity, especially when auto variables are involved.

For example, suppose you encounter this in a code base:

    auto result = findRecord( /* arguments */ );
    if (result == 0) {}

If you don’t happen to know (or can’t easily find out) what findRecord returns, it may not be clear whether result is a pointer type or an integral type. After all, 0 (what result is tested against) could go either way. If you see the following, on the other hand,

    auto result = findRecord( /* arguments */ );
    if (result == nullptr) {}

there’s no ambiguity: result must be a pointer type.


nullptr shines especially brightly when templates enter the picture. Suppose you have some functions that should be called only when the appropriate mutex has been locked. Each function takes a different kind of pointer:

    int f1(std::shared_ptr<Widget> spw);      // call these only when
    double f2(std::unique_ptr<Widget> upw);   // the appropriate
    bool f3(Widget* pw);                      // mutex is locked

Calling code that wants to pass null pointers could look like this:

    std::mutex f1m, f2m, f3m;                 // mutexes for f1, f2, and f3
    using MuxGuard = std::lock_guard<std::mutex>;
    {
        MuxGuard g(f1m);                      // lock mutex for f1
        auto result = f1(0);                  // pass 0 as null ptr to f1
    }                                         // unlock mutex
    
    {
        MuxGuard g(f2m);                      // lock mutex for f2
        auto result = f2(NULL);               // pass NULL as null ptr to f2
    }                                         // unlock mutex

    {
        MuxGuard g(f3m);                      // lock mutex for f3
        auto result = f3(nullptr);            // pass nullptr as null ptr to f3
    }                                         // unlock mutex

The failure to use nullptr in the first two calls in this code is sad, but the code works, and that counts for something. However, the repeated pattern in the calling code—lock mutex, call function, unlock mutex—is more than sad. It’s disturbing. This kind of source code duplication is one of the things that templates are designed to avoid, so let’s templatize the pattern:

    template<typename FuncType, typename MuxType, typename PtrType>
    auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
    {
        MuxGuard g(mutex);
        return func(ptr);
    }

If the return type of this function auto … -> decltype(func(ptr) has you scratching your head, you’ll see that in C++14, the return type could be reduced to a simple decltype(auto):

    template<typename FuncType, typename MuxType, typename PtrType>
    decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
    {
        MuxGuard g(mutex);
        return func(ptr);
    }

Given the lockAndCall template (either version), callers can write code like this:

    auto result1 = lockAndCall(f1, f1m, 0);           // error!
    auto result2 = lockAndCall(f2, f2m, NULL);        // error!
    auto result3 = lockAndCall(f3, f3m, nullptr);     // fine

The fact that template type deduction deduces the “wrong” types for 0 and NULL (i.e., their true types, rather than their fallback meaning as a representation for a null pointer) is the most compelling reason to use nullptr instead of 0 or NULL when you want to refer to a null pointer. With nullptr, templates pose no special challenge. Combined with the fact that nullptr doesn’t suffer from the overload resolution surprises that 0 and NULL are susceptible to, the case is ironclad. When you want to refer to a null pointer, use nullptr, not 0 or NULL.

Upvotes: 1

Some programmer dude
Some programmer dude

Reputation: 409482

NULL is more or less a left-over from simpler days, when C++ was much closer to its ancestor C. Since C++ was first standardized (and even before) it was recommended to use 0 for null-pointers. C++11 added nullptr which is a drop-in replacement for both 0 and NULL.

However the type of nullptr is std::nullptr_t which can be useful for templates and function arguments (for overloading).

Upvotes: 5

Related Questions