Reputation: 39
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
Reputation: 6667
A really nice book I was recommended recently: Effective Modern C++ by Scott Meyers.
Neither
0
norNULL
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 isstd::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 whetherresult
is a pointer type or an integral type. After all,0
(whatresult
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 simpledecltype(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
andNULL
(i.e., their true types, rather than their fallback meaning as a representation for a null pointer) is the most compelling reason to usenullptr
instead of0
orNULL
when you want to refer to a null pointer. Withnullptr
, templates pose no special challenge. Combined with the fact thatnullptr
doesn’t suffer from the overload resolution surprises that0
andNULL
are susceptible to, the case is ironclad. When you want to refer to a null pointer, usenullptr
, not0
orNULL
.
Upvotes: 1
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