If the return type is int, you can't return a NULL. To show an error, you could instead return a special value like zero or -1, if you check for that value in any calling function. Lots of functions return nonnegative numbers on success, or -1 on error.
NULL cannot be stored in an int variable, unlike in SQL, for example. If you ignore the warning and return NULL anyway, then NULL will be casted to zero. The calling function won't be able to tell whether you returned NULL or zero.
If your function only needs to indicate success or failure, then it's common to return 1 for success, and zero for failure. Zero means "false" when treated as a boolean value (like in if statements), and non-zero means "true."
If the return type is int, you can't return a NULL. To show an error, you could instead return a special value like zero or -1, if you check for that value in any calling function. Lots of functions return nonnegative numbers on success, or -1 on error.
NULL cannot be stored in an int variable, unlike in SQL, for example. If you ignore the warning and return NULL anyway, then NULL will be casted to zero. The calling function won't be able to tell whether you returned NULL or zero.
If your function only needs to indicate success or failure, then it's common to return 1 for success, and zero for failure. Zero means "false" when treated as a boolean value (like in if statements), and non-zero means "true."
It looks like you've misunderstood what NULL means in C. Types are not nullable. NULL is effectively just a shorthand for the pointer with value 0! And int is not a pointer.
Maybe there are some basic things you should rethink:
First, only pointers can be NULL, but not objects. Hence, if you return an object of type struct Stack (which is not a pointer), you cannot return NULL but just an instance of struct Stack.
Second, passing in and returning an object of struct Stack by value will result in copying the respective object; I think that passing references or pointers would be a better choice; and - if you pass in and return a pointer, you could also return NULL to indicate a full stack or some other issue.
The problem is that your function must return a value that has the type Stack.
The code you provided doesn't define the type of NULL, but, since you're not seeing another error and you're assigning it to node, I would guess that the type of NULL is StackNode *... or, at least, something compatible with that.
So, there's your problem. You're trying to return something with the type StackNode * when your function claims to return a Stack.
Your best bet is to redesign this function that returns the result of the push operation and not the Stack itself.
if( mystruct == NULL )
mystruct is not a pointer, so you cannot compare it with NULL.
You have three options:
- Add a status field to
MyStructto indicate whether the struct has been initialized correctly. - Allocate the struct on the heap and return it by pointer.
- Pass the structure as a pointer argument and return a status code (thanks @Potatoswatter).
A structure is not a pointer. If you want to be able to return NULL, you're going to have to allocate the structure on the heap so you can return a pointer to it, and let the caller clean up afterwards.
That way, you can indicate failure, something like:
MyStruct *init_mystruct (void) {
MyStruct *mystruct = malloc (sizeof (*mystruct));
if (mystruct != NULL)
return NULL;
int is_ok = 1;
/* do something ... */
/* everything is OK */
if( is_ok )
return mystruct;
/* something went wrong */
free (mystruct);
return NULL;
}
int main (void) {
MyStruct *mystruct = init_mystruct();
if (mystruct == NULL) {
/* error handler */
return -1;
}
free (mystruct);
return 0;
}
NULL is a pointer literal which is defined to contain a special value.
One possible definition is:
#define NULL ((void *)0)
For more detail you can read this faq
About const string& foo(), I believe you mean C++'s std::string.
std::string has no implicit constructor that initialize it with NULL pointer.
So you should use some exception or empty string to indicate an error to the caller. (If you are not throwing, you must return an std::string. Even if the object returned is local, its life is prolonged when kept in a local constant reference. But returning some other type and expecting an implicit conversion is not a good idea.)
Answer to your question: Because NULL is a literal, no temporary object may be created most of the time and actual value can be directly returned to the caller.
This function
const string& foo();
does not return a pointer. It returns a constant reference to an object of type std::string. So its return value may not be assigned to a pointer.
According to the C++ Standard
4.10 Pointer conversions [conv.ptr]
1 A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion (4.4). A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t. [ Note: The resulting prvalue is not a null pointer value. —end note ]
So when you use null pointer constant defined with macro NULL it is assigned to the return pointer of the function that will contain null pointer value of type node *
NULL is a pointer value - or rather a null-pointer value.
NULL means that the function can't find where your pointer should point to - for example if you want to open a file, but it doesn't work your file pointer is returned as NULL. So you can test the value of a pointer and check to see if it worked or not.
If you are writing a routine
int length()
then you could return a negative value if length is unable to read the length of whatever you send it - this would be a way of indicating an error, because normally lengths can never be negative....
It is a matter of convention and you should clearly have one in your head and document it (at least in comments).
Sometimes a pointer really should always point to a valid address (see this intSwap example, both arguments should be valid pointers). At other times, it should either be such a valid address, or be NULL. Conceptually the pointer type is then by convention a sum type (between genuine pointer addresses and the special NULL value).
Notice that the C language does not have a type (or a notation) which enforces that some given pointer is always valid and non-null. BTW, with GCC specifically, you can annotate a function with __attribute__ using nonnull to express that a given argument is never null.
A typical example is FILE* pointers in <stdio.h>. The fopen function is documented to be able to return NULL (on failure), or some valid pointer. But the fprintf function is expecting a valid pointer (and passing NULL to it as the first argument is some undefined behavior, often a segmentation fault; and UB is really bad).
Some non-portable programs even use several "special" pointer values (which should not be dereferenced), e.g. (on Linux/x86-64) #define SPECIAL_SLOT (void*)((intptr_t)-1) (which we know that on Linux it is never a valid address). Then we could have the convention that a pointer is a valid pointer to a valid memory zone, or NULL or SPECIAL_SLOT (hence, if seen as an abstract data type, it is a sum type of two distinct invalid pointers NULL and SPECIAL_SLOT and the set of valid addresses). Another example is MAP_FAILURE as result of mmap(2) on Linux.
BTW, when using pointers in C to heap allocated data (indirectly obtained with malloc), you also need conventions about who is in charge of releasing the data (by using free, often thru a supplied function to free a data and all its internal stuff).
Good C programming requires many explicit conventions regarding pointers, and it is essential to understand them precisely and document them well. Look for example[s] into GTK. Read also about restrict.
GCC sees the undefined behaviour (UB) visible at compile time and decides to just return NULL on purpose. This is good: noisy failure right away on first use of a value is easier to debug. Returning NULL was a new feature somewhere around GCC5; as @P__J__'s answer shows on Godbolt, GCC4.9 prints non-null stack addresses.
Other compilers may behave differently, but any decent compile will warn about this error. See also What Every C Programmer Should Know About Undefined Behavior
Or with optimization disabled, you could use a tmp variable to hide the UB from the compiler. Like int *p = &C; return p; because gcc -O0 doesn't optimize across statements. (Or with optimization enabled, make that pointer variable volatile to launder a value through it, hiding the source of the pointer value from the optimizer.)
Copy#include <stdio.h>
int* C() {
int C = 10;
int *volatile p = &C; // volatile pointer to plain int
return p; // still UB, but hidden from the compiler
}
int main()
{
int* D = C();
printf("%p\n", (void *)D);
if (D){
printf("%#x\n", *D); // in theory should be passing an unsigned int for %x
}
}
Compiling and running on the Godbolt compiler explorer, with gcc10.1 -O3 for x86-64:
Copy0x7ffcdbf188e4
0x7ffc
Interestingly, the dead store to int C optimized away, although it does still have an address. It has its address taken, but the var holding the address doesn't escape the function until int C goes out of scope at the same time that address is returned. Thus no well-defined accesses to the 10 value are possible, and it is valid for the compiler to make this optimization. Making int C volatile as well would give us the value.
The asm for C() is:
CopyC:
lea rax, [rsp-12] # address in the red-zone, below RSP
mov QWORD PTR [rsp-8], rax # store to a volatile local var, also in the red zone
mov rax, QWORD PTR [rsp-8] # reload it as return value
ret
The version that actually runs is inlined into main and behaves similarly. It's loading some garbage value from the callstack that was left there, probably the top half of an address. (x86-64's 64-bit addresses only have 48 significant bits. The low half of the canonical range always has 16 leading zero bits).
But it's memory that wasn't written by main, so perhaps an address used by some function that ran before main.
Copy// B will be 5 even when uninitialised due to the B stack frame using
// the old memory layout of A
int B;
Nothing about that is guaranteed. It's just luck that that happens to work out when optimization is disabled. With a normal level of optimization like -O2, reading an uninitialized variable might just read as 0 if the compiler can see that at compile time. Definitely no need for it to load from the stack.
And the other function would have optimized away a dead store.
GCC also warns for use-uninitialized.
It is an undefined behaviour (UB) but many modern compilers when they detect it return the reference to the automatic storage variable return NULL as a precaution (for example newer versions of gcc).
example here: https://godbolt.org/z/H-zU4C
[H]ow do I return NULL from a integer result type function?
You don't. NULL is a macro representing a value of type void *. It is not an int, so a function returning int cannot return NULL.
Now, it is possible to convert NULL or any other pointer to type int, but the result of such a conversion is a valid, ordinary int. You seem to be looking instead for some kind of distinguished value, but there is none available unless you reserve such a value yourself. For example, you might reserve INT_MIN for that purpose. Of the built-in types, only pointer types afford general-purpose distinguished values (null pointers).
To provide for your function to signal failure to the caller, you have a couple of alternatives to reserving a value. The most common one is to use the function's return value only to report on the success or failure of the call, and to deliver any output -- in your case a node's value -- via a pointer argument:
int valueOf(t_node *node, int n, int *result) {
int current = 0;
while (current < n && node != NULL) {
node = node->next;
current += 1;
}
if (node == NULL) {
// no such node -- return a failure code
return 0;
} else {
// current == n
*result = node->value;
return 1;
}
}
In C, NULL is just a synonym for 0. So you really only need to do this...
if (current > n) {
return 0;
}
However, NULL usually refers to a pointer value that is undefined and not an integer. In C, integer values are not references as they are in many interpreted languages. They are scalar and can't be referred to implicitly with a pointer.
If you want to indicate an error condition or undefined behavior when current > n, you will have to provide a separate mechanism for indicating that the value isn't usable. Usually, C functions will return a -1 on an error. Since you are using the integer return for a value, that would mean that a valid value could never be -1.
It looks like you're handling a linked list and you want to limit the number of items to be checked. A possible way around this might be...
int valueOf(t_node *node, int n, int *val){
int current = 0;
int value;
while (node -> next != NULL) {
value = node -> val;
if (current == n) {
// This notation is for dereferencing a pointer.
*val = value;
return 0;
}
if (current > n) {
return -1;
}
node = node -> next;
current += 1;
}
// This method also gives you a way to indicate that
// you came to the end of the list. Your code snippet
// would have returned an undefined value if node->next == null
return -1;
}
is
return;the same asreturn NULL;in C++?
No.
return is used to "break" out from a function that has no return value, i.e. a return type of void.
return NULL returns the value NULL, and the return type of the function it's found in must be compatible with NULL.
I understand that in C++,
return NULL;is the same asreturn 0;in the context of pointers.
Sort of. NULL may not be equivalent to 0, but it will at least convert to something that is.
Obviously for integers, this is not the case as NULL cannot be added, subtracted, etc.
You can perform addition and subtraction to pointers just fine. However, NULL must have integral type (4.10/1 and 18.1/4 in C++03) anyway so it's moot. NULL may very well be a macro that expands to 0 or 0UL.
Some modern compilers will at least warn you if it was actually NULL you wrote, though.
And that it is encouraged by some to use 0 instead of NULL for pointers because it is more convenient for portability. I'm curious if this is another instance where an equivalence occurs.
No. And I disagree with this advice. Though I can see where it's coming from, since NULL's exact definition varies across implementations, using NULL will make it much easier to replace with nullptr when you switch to C++11, and if nothing else is self-documenting.
return with no expression works only if your function is declared void, in a constructor, or in a destructor. If you try to return nothing from a function that returns an int, a double, etc., your program will not compile:
error: return-statement with no value, in function returning ‘int’
According to §6.6.3/2 of C++11:
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type void, a constructor (12.1), or a destructor (12.4).
(thanks sftrabbit for the excellent comment).
In C, NULL is a macro that expands either to 0 or (void*)0 (or something that has a similar effect).
In the first case, you can not differentiate between NULL and 0, because they are literally the same.
In the second case, your code will cause a compile error, because you can't compare an integer variable with a pointer.
First some background ...
The macros are
NULLwhich expands to an implementation-defined null pointer constant; C11 §7.19 3
NULL typically is an integer constant 0 or (void*)0 or the like. It may have a different implementation or type - It could be ((int*) 0xDEADBEEF) as strange as that may be.
NULL might be type int. It might be type void * or something else. The type of NULL is not defined.
When the null pointer constant NULL is cast to any pointer, is is a null pointer. An integer 0 cast to a pointer is also a null pointer. A system could have many different (bit-wise) null pointers. They all compare equally to each other. They all compare unequally to any valid object/function. Recall this compare is done as pointers, not integers.
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. C11 §6.3.2.3 3
int x;
if (&x == NULL) ... // this is false
So after all that chapter and verse how to distinguish NULL from 0?
If the macro NULL is defined as an int 0 - it is game over - there is no difference between 0 and NULL.
If NULL is not an int, then code can use _Generic() to differentiate NULL and 0. This does not help OP's "Any change made can only be made within the function itself." requirement as that function accepts an int augment.
If NULL is an int that has a different bit-pattern than 0, then a simple memcmp() can differentiate.
I suspect the whole reason for this exercise is to realize there is no portable method to distinguish NULL from 0.
I think you need something like
CopyNormal* Sphere::hit(Ray ray) {
//stuff is done here
if(something happens) {
return NULL;
}
//other stuff
return new Normal(something, somethingElse);
}
to be able to return NULL;
There are several fairly standard ways of doing this. There are different tradeoffs for the methods, which I'm not going to go into here.
Method 1: Throw an exception on failure.
CopyNormal Sphere::hit(Ray ray)
{
//stuff is done here
if(something happens) {
throw InvalidIntersection;
}
//other stuff
return Normal(something, somethingElse);
}
void example(Ray r)
{
try {
Normal n = s.hit(r);
... SUCCESS CASE ...
}
catch( InvalidIntersection& )
{
... FAILURE CASE ...
}
}
Method 2 return a pointer to a newly allocated object. (You could also use smart pointers, or auto_ptrs to make this a little neater).
CopyNormal* Sphere::hit(Ray ray)
{
//stuff is done here
if(something happens) {
return NULL
}
//other stuff
return new Normal(something, somethingElse);
}
void example(Ray ray)
{
Normal * n = s.hit(ray);
if(!n) {
... FAILURE CASE ...
} else {
... SUCCESS CASE ...
delete n;
}
}
Method 3 is to update an existing object. (You could pass a reference, but a convention I use is that any output parameter is passed by pointer).
Copybool Sphere::hit(Ray ray, Normal* n)
{
//stuff is done here
if(something happens) {
return false
}
//other stuff
if(n) *n = Normal(something, somethingElse);
return true;
}
void example(Ray ray)
{
Normal n;
if( s.hit(ray, &n) ) {
... SUCCESS CASE ...
} else {
... FAILURE CASE ...
}
}
Method 4: Return an optional<Normal> (using boost or similar)
Copyoptional<Normal> Sphere::hit(Ray ray)
{
//stuff is done here
if(something happens) {
return optional<Normal>();
}
//other stuff
return optional<Normal>(Normal(something, somethingElse));
}
void example(Ray ray)
{
optional<Normal> n = s.hit(ray);
if( n ) {
... SUCCESS CASE (use *n)...
} else {
... FAILURE CASE ...
}
}
The code for pushing (and popping) the stack is wrong. Also, I was confused for a while reading your code because top actually represents array of stack elements, not just the top item.
In particular, malloc expects a number of bytes, however you have passed it a number of elements. And it is poor style to use void * when you mean void **.
I'd suggest making your code more readable by using [] notation and renaming top:
void **new_content = malloc( (stack->size + 1) * sizeof *new_content);
if ( !new_content)
// error handling...
if ( stack->size > 0 )
memcpy( &new_content[1], &stack->content[0], stack->size * sizeof *new_content );
new_content[0] = item;
free(stack->content);
stack->content = new_content;
++stack_size;
Now, this is actually about the worst possible way to implement a stack, in terms of efficiency. Every single push and pop operation you have to do an allocation and copy the entire stack. If you actually make your stack have its "top" at the end, then things become a whole lot simpler:
void **new_content = realloc( stack->content, (stack->size + 1) * sizeof *new_content );
if ( !new_content )
// error handling...
new_content[stack->size++] = item;
stack->content = new_content;
Similar comments apply to your pop function in both varieties.
In stack_push():
void *new_top = malloc(stack->size);
memcpy(new_top + sizeof(void*), stack->top, (stack->size - 1) * sizeof(void *));
This has several problems:
- You are allocating the wrong size. You want to allocate
stack->sizenumber of items times the size of whateverstack->toppoints to. - You must not call
memcpy()whenstack->topis 0. The behaviour is undefined if you copy to or from a null pointer. - You try to perform pointer arithmetic on a
void *. This is a compiler extension (eg. gcc) and is not possible in standard C.
The easiest way to solve these problems is to declare new_top to be the same type as stack->top:
void **old_top = stack->top;
void **new_top = malloc(stack->size * sizeof *new_top);
if (stack->top) {
memcpy (new_top + 1, stack->top, (stack->size - 1) * sizeof *new_top);
}
stack->top = new_top;
Similarly, in stack_pop():
void **old_top = stack->top;
void *item = *stack->top;
void **new_top = malloc(stack->size * sizeof *new_top);
if (stack->size) {
memcpy(new_top, stack->top + 1, stack->size * sizeof *new_top);
}
stack->top = new_top;
When does
malloc()in C returnNULL?
malloc() returns a null pointer when it fails to allocate the needed space.
This can be due to:
- Out-of-memory in the machine (not enough bytes)
- Out-of-memory for the process (OS may limit space per process)
- Out of memory handles (Too many allocations, some allocators has this limit)
- Too fragmented (Enough memory exist, but allocator can't/does not want to re-organize into a continuous block).
- All sorts of reasons like your process is not worthy of more.
malloc(0) may return a null pointer. C17/18 adds a bit.
If the size of the space requested is zero, the behavior is implementation-defined:
either a null pointer is returned to indicate an error,
or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
malloc(0) may return a null pointer. (pre-C17/18)
If the size of the space requested is zero, the behavior is implementation-defined:
either a null pointer is returned,
or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
The "to indicate an error" of C17/18 implies to me that a null pointer return is an error, perhaps due to one of the above 5 reasons and a non-erroring malloc(0) does not return a null pointer.
I see this as a trend to have p = malloc(n); if (p==NULL) error(); to always be true on error even if n is 0. Else one might code as if (p==NULL && n > 0) error();
If code wants to tolerate an allocation of zero to return NULL as a non-error, better to form a helper function to test for n == 0 and return NULL than call malloc().
Conversely a return of non-null pointer does not always mean this is enough memory. See Why is malloc not “using up” the memory on my computer?
If for some reason, the memory that you ask to malloc can't be allocated or sometimes if you ask for 0 memory, it returns NULL.
Check the documentation
Value of return type std::vector<int> cannot be nullptr.
The most straightforward way in this case is to return std::unique_ptr<std::vector<int>> instead - in this case it's possible to return nullptr.
Other options:
- throw an exception in case of fail
- use
optional<std::vector<int>>as return type (eitherboost::optionalorstd::optionalif your compiler has this C++17 feature) - return
boolparameter and havestd::vector<int>&as output parameter of function
The best way to go really depends on the use case. For example, if result vector of size 0 is equivalent to 'fail' - feel free to use this kind of knowledge in your code and just check if return vector is empty to know whether function failed or succeed.
In my practice I almost always stick to return optional (boost or std, depending on environment restriction).
Such interface is the most clear way to state the fact that 'result can either be present or not'.
To sum up - this is not a problem with the only one right solution. Possible options are listed above - and it's only a matter of your personal experience and environmental restrictions/convetions - which option to choose.
You could return a std::unique_ptr<std::vector<int>> and check that value, or throw an exception, or check vector.size() == 0 etc.
StackOverflow has a good discussion about this exact topic in this Q&A. In the top rated question, kronoz notes:
Returning null is usually the best idea if you intend to indicate that no data is available.
An empty object implies data has been returned, whereas returning null clearly indicates that nothing has been returned.
Additionally, returning a null will result in a null exception if you attempt to access members in the object, which can be useful for highlighting buggy code - attempting to access a member of nothing makes no sense. Accessing members of an empty object will not fail meaning bugs can go undiscovered.
Personally, I like to return empty strings for functions that return strings to minimize the amount of error handling that needs to be put in place. However, you'll need to make sure that the group that your working with will follow the same convention - otherwise the benefits of this decision won't be achieved.
However, as the poster in the SO answer noted, nulls should probably be returned if an object is expected so that there is no doubt about whether data is being returned.
In the end, there's no single best way of doing things. Building a team consensus will ultimately drive your team's best practices.
In all the code I write, I avoid returning null from a function. I read that in Clean Code.
The problem with using null is that the person using the interface doesn't know if null is a possible outcome, and whether they have to check for it, because there's no not null reference type.
In F# you can return an option type, which can be some(Person) or none, so it's obvious to the caller that they have to check.
The analogous C# (anti-)pattern is the Try... method:
public bool TryFindPerson(int personId, out Person result);
Now I know people have said they hate the Try... pattern because having an output parameter breaks the ideas of a pure function, but it's really no different than:
class FindResult<T>
{
public FindResult(bool found, T result)
{
this.Found = found;
this.Result = result;
}
public bool Found { get; private set; }
// Only valid if Found is true
public T Result { get; private set;
}
public FindResult<Person> FindPerson(int personId);
...and to be honest you can assume that every .NET programmer knows about the Try... pattern because it's used internally by the .NET framework. That means they don't have to read the documentation to understand what it does, which is more important to me than sticking to some purist's view of functions (understanding that result is an out parameter, not a ref parameter).
So I'd go with TryFindPerson because you seem to indicate it's perfectly normal to be unable to find it.
If, on the other hand, there's no logical reason that the caller would ever provide a personId that didn't exist, I would probably do this:
public Person GetPerson(int personId);
...and then I'd throw an exception if it was invalid. The Get... prefix implies that the caller knows it should succeed.
C++ objects can never be null or empty. Pointers can hold a null pointer value indicating they point at nothing.
The typical solution would be to throw an exception. Otherwise, use a pointer; just make sure you aren't returning the address of a temporary.
I wouldn't recommend trying to teach yourself C++ with knowledge from other languages, you'll hurt yourself. Grab a good beginner-level book, it's the best way to learn.
Throw an exception. That's what they're for.
Since both are pointing to the same object, if either
objord_headeris changed, the change will be reflected in the other.
That is incorrect. If the contents of what they point to is changed, that change can be seen through either pointer but if one of them is changed so that it points to something different, the other will still point to the previous object.
Simple example:
int i = 10;
int j = 20;
int* ptr1 = &i;
int* ptr2 = ptr1;
At this point, both the pointers point to the same object, i. Value of i can be changed by:
Directly by assigning a value to
i.i = 15;Indirectly by assigning a value to where
ptr1points to.*ptr1 = 15;Indirectly by assigning a value to where
ptr2points to.*ptr2 = 15;
However, you can change where ptr1 points to by using:
ptr1 = &j;
Now, ptr1 points to j but ptr2 still points to i.
Any changes made to i will be visible through ptr2 but not ptr1.
Any changes made to j will be visible through ptr1 but not ptr2.
Isn't
objalso pointing to null, since both object were pointing to the same object?
The answer should be clear now. obj continues to point to what d_header used to point to. It is not NULL.
So what is the deal of returning a Null pointer?
The function does not necessarily return a NULL pointer. The function returns whatever d_header used to point to before it was changed to be nullptr. It could be a NULL pointer if d_header used to be NULL before the call to the function.
is
nullptrin this case would be the same asdelete?
No, it is not. There are two different operations. Assigning a pointer to nullptr does not automatically mean that delete gets called on the pointer. If you need to deallocate memory that a pointer points to, you'll have to call to call delete explicitly.
Q: "Isn't obj also pointing to null, since both object were pointing to the same object?"
No, both obj and d_heater contain addresses in memory. Changing one does not change the other. This is easy to see if you think about non-pointer variables:
int foo = 13;
int bar = foo;
foo = 42;
Obviously we know that bar still holds 13, and in the same way obj still holds the original address.
If you want obj to keep the same value as d_heater you can make it a reference, then obj and d_heater are the same variable they don't simply share the same value. We can see this again looking at non-pointer variables, but this time let's make bar a reference:
int foo = 13;
int& bar = foo;
foo = 42;
Now both foo and bar will equal 42. If you want to accomplish the same thing with obj make it a pointer reference:
WaterHeater*& obj = d_heater;
You can see a more detailed example here: http://ideone.com/I8CTba
Q. "Is nullptr in this case would be the same as delete?"
No, again d_heater is only an address. If you assign a new value to d_heater it is simply referencing a different point in memory. If by reassigning d_heater you lose the address of dynamically allocated memory that's very bad, it's known as a memory leak. To prevent leaking you must always release dynamically allocated memory before losing the last address to your dynamically allocated memory (call delete for memory allocated with new.)
That said, use of dynamic memory allocation is best left to the standard libraries, unless you really know what you're doing. So I'd strongly recommend you look at using auto-pointers. In C++11 those are unique_ptr and shared_ptr.
Actually, you can use a literal 0 anyplace you would use NULL.
Section 6.3.2.3p3 of the C standard states:
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
And section 7.19p3 states:
The macros are:
CopyNULLwhich expands to an implementation-defined null pointer constant
So 0 qualifies as a null pointer constant, as does (void *)0 and NULL. The use of NULL is preferred however as it makes it more evident to the reader that a null pointer is being used and not the integer value 0.
NULL is used to make it clear it is a pointer type.
Ideally, the C implementation would define NULL as ((void *) 0) or something equivalent, and programmers would always use NULL when they want a null pointer constant.
If this is done, then, when a programmer has, for example, an int *x and accidentally writes *x = NULL;, then the compiler can recognize that a mistake has been made, because the left side of = has type int, and the right side has type void *, and this is not a proper combination for assignment.
In contrast, if the programmer accidentally writes *x = 0; instead of x = 0;, then the compiler cannot recognize this mistake, because the left side has type int, and the right side has type int, and that is a valid combination.
Thus, when NULL is defined well and is used, mistakes are detected earlier.
In particular answer to your question “Is there a context in which just plain literal 0 would not work exactly the same?”:
- In correct code,
NULLand0may be used interchangeably as null pointer constants. 0will function as an integer (non-pointer) constant, butNULLmight not, depending on how the C implementation defines it.- For the purpose of detecting errors,
NULLand0do not work exactly the same; usingNULLwith a good definition serves to help detect some mistakes that using0does not.
The C standard allows 0 to be used for null pointer constants for historic reasons. However, this is not beneficial except for allowing previously written code to compile in compilers using current C standards. New code should avoid using 0 as a null pointer constant.
Why are you getting the warning?
You have enabled the nullable reference types (NRT) feature of C#. This requires you to explicitly specify when a null may be returned. So change the signature to:
public TEntity? Get(Guid id)
{
// Returns a TEntity on find, null on a miss
return _entities.Find(id);
}
And the warning will go away.
What is the use of NRTs?
Other recent changes - specifically around pattern matching - then tie in really nicely with NRT's. In the past, the way to implement the "try get pattern" in C# was to use:
public bool TryGet(Guid id, out TEntity entity)
Functional languages offer a better approach to this: the maybe (or option) type, which is a discriminated union (DU) of some value and none. Whilst C# doesn't yet support DU's, NRT's effectively provide that maybe type (or a poor man's equivalent) as TEntity? is functionally equivalent to Maybe<TEntity>:
if (Get(someId) is TEntity entity)
{
// do something with entity as it's guaranteed not null here
}
else
{
// handle the fact that no value was returned
}
Whilst you can use this type of pattern matching without using NRTs, the latter assists other developers as it makes clear that the method will return null to indicate no value. Change the name to TryGet and C# now provides that functional style try get pattern:
public TEntity? TryGet(Guid id) => _entities.Find(id);
And with the new match expression, we can avoid out parameters, mutating values etc and have a truly functional way of trying to get an entity and creating one if it doesn't exist:
var entity = TryGet(someId) switch {
TEntity e => e,
_ => Create(someId)
};
But is it wrong to return null?
There has been vast amounts written on why null was the billion dollar mistake. As a very crude rule of thumb, the existence of null likely indicates a bug. But it's only a crude rule of thumb as there are legitimate use-cases for null in the absence of Maybe<T>. NRT's bridge that gap: they provide a relatively safe way of using null to indicate no value. So I'd suggest - for those using newer versions of C# - there is nothing wrong with returning null as long as you enable the NRT feature and you stay on top of those CS8603 warnings. Enable "treat warnings as errors" and you definitely will stay on top of them.
David Arno already answered your question about the specific warning, so I'd like to address your general question:
What's wrong with returning null?
Nothing, as long as the consumer of your method is aware that null might be returned, and, thus, it is their responsibility to react appropriately.
If your language supports null annotations: Use them. If it doesn't (e.g., if you are stuck with a classic .NET Framework 4.8 project), my go-to solution is to name the method appropriately, i.e., GetOrNull, GetIfExists, etc. Often, I have both (Get throwing an exception and GetOrNull returning null), if I need to cover both use cases:
public TEntity GetOrNull(Guid id) => _entities.Find(id);
public TEntity Get(Guid id) =>
GetOrNull(id) ?? throw new ArgumentException($"Entity {id} not found.");
There are several practical reasons why functions like fopen return pointers to instead of instances of struct types:
- You want to hide the representation of the
structtype from the user; - You're allocating an object dynamically;
- You're referring to a single instance of an object via multiple references;
In the case of types like FILE *, it's because you don't want to expose details of the type's representation to the user - a FILE * object serves as an opaque handle, and you just pass that handle to various I/O routines (and while FILE is often implemented as a struct type, it doesn't have to be).
So, you can expose an incomplete struct type in a header somewhere:
typedef struct __some_internal_stream_implementation FILE;
While you cannot declare an instance of an incomplete type, you can declare a pointer to it. So I can create a FILE * and assign to it through fopen, freopen, etc., but I can't directly manipulate the object it points to.
It's also likely that the fopen function is allocating a FILE object dynamically, using malloc or similar. In that case, it makes sense to return a pointer.
Finally, it's possible you're storing some kind of state in a struct object, and you need to make that state available in several different places. If you returned instances of the struct type, those instances would be separate objects in memory from each other, and would eventually get out of sync. By returning a pointer to a single object, everyone's referring to the same object.
There are two ways of "returning a structure." You can return a copy of the data, or you can return a reference (pointer) to it. It's generally preferred to return (and pass around in general) a pointer, for a couple of reasons.
First, copying a structure takes a lot more CPU time than copying a pointer. If this is something your code does frequently, it can cause a noticeable performance difference.
Second, no matter how many times you copy a pointer around, it's still pointing to the same structure in memory. All modifications to it will be reflected on the same structure. But if you copy the structure itself, and then make a modification, the change only shows up on that copy. Any code that holds a different copy won't see the change. Sometimes, very rarely, this is what you want, but most of the time it's not, and it can cause bugs if you get it wrong.