Deleting an untyped shared_ptr - c++

I am working on wrapping a C++ library into a C bridge.
All objects, I’d like to maintain with shared_ptrs on the heap like:
void* makeFoo() {
return new shared_ptr<void>(shared_ptr::make_shared<Foo>());
}
Can I use a generic destroy like this:
void destroy(void* p) {
delete static_cast<shared_ptr<void>*> p;
}
Or is there a cleaner way?

The type of the argument to delete must match the actual type of the thing you're deleting (otherwise how would the right destructor be invoked, for example?), or at least be a base type in a polymorphic hierarchy so the destructor can be found virtually ([expr.delete]/3).
So, no, you can't do that.

There are a two things at play here:
When you call new SomeType() ... then you need to call delete pointer where pointer has type SomeType * and points to the object allocated by that new expression. There are extensions to this rule with regards to base classes, but inheritance is not involved here, so we'll leave it at that.
shared_ptr<Foo> manages not only the Foo object, but also a "deleter" which knows how to destruct the Foo object. When you construct one shared_ptr from another, then that deleter gets passed on. This allows for "type erasure":
shared_ptr<Foo> typed = make_shared<Foo>();
shared_ptr<void> erased = typed;
Here, erased does no longer have compile time information about the type of the object it points to (that information was "erased"), but still has runtime information (the deleter) about the type of the object.
So to make this work, you need to make sure that you don't violate point 1 above; you need to delete the same type which you allocated with new: a shared_ptr<void>. This shared_ptr<void> needs to be constructed from a shared_ptr<Foo> because then it has a deleter which knows how to destruct a Foo:
void* makeFoo() {
shared_ptr<Foo> with_type = make_shared<Foo>();
shared_ptr<void> type_erased = with_type; // Just for illustration, merge with line below!
return new shared_ptr<void>(type_erased);
}
void destroy(void * ptr) {
shared_ptr<void> * original_ptr = ptr;
delete original_ptr;
}
makeFoo returns a pointer to a shared_ptr<void>. Just with the type info stripped, i.e. as void *.
destroy assumes it is passed such a pointer. Its delete calls the destructor of shared_ptr<void>. Because the shared_ptr<void> has the deleter of the original shared_ptr<Foo>, it knows how to actually destruct the object (Foo).
Side note: OP's code changed quite a few times, but still has basic syntax errors. This is not valid C++!!
delete <shared_ptr<void>*> p;

No. There is no such "generic delete".
Alternative solution: You can insert the std::shared_ptr<void> into a map, using the address of the dynamic object as key. The deallocation function can erase the shared pointer from the map.

Related

Convert a shared_ptr to regular a pointer

We have a function that returns a new allocated object as a output argument (ref to pointer).
MyFunc(MyObject*& obj)
{
obj = new MyObject();
}
Which is called like so:
Object* obj;
MyFunc(obj);
Internally the function does quite a bit and uses shared_ptr for memory management. When it is done, the object we would like to return is referenced by a shared_ptr. I am struggling on how to return our new allocated object as a regular pointer.
We would like to continue to use shared_ptr internally to reduce risks, but it does not seem to make sense to return a shared_ptr as the caller takes complete ownership over the returned value (the called function or object no longer needs or keeps a reference to the returned data) and we want them to have flexibility.
Does anyone have any suggestions for allowing us to use shared_ptr internally but have a regular pointer interface? Thanks
If for some reason you cannot/want not use std::unique_ptr or std::auto_ptr (for example if you need to have multiple owners internally during creation for some reason or your underlying methods require std::shared_ptr to be passed around), you can still make it work with std::shared_ptr by using custom deleter, as described here: https://stackoverflow.com/a/5995770/1274747
In the principle, after you're done before the return, you switch the deleter to not actually delete the instance (make the deleter "null") and then return by shared_ptr get(). Even after all shared_ptr objects are destroyed, the memory will not be deleted (as the nulled deleter will skip the deletion).
There is also a link in the comments not so well visible, which might be of your interest:
http://paste.ubuntu.com/23866812/
(not sure though if it would really work without the shared ownership of the switch in all cases, would need to test)
EDIT
As expected, with the linked simple disarmable deleter from the pastebin you need to be careful, because the deleter is actually copied for storing in std::shared_ptr.
But you can still make it work by using std::ref:
MyFunc(MyObject*& obj)
{
DisarmableDelete<MyObject> deleter;
std::shared_ptr<MyObject> ptr(new MyObject(), std::ref(deleter));
// do what is necessary to setup the object - protected by deleter
// ...
// disarm before return
deleter._armed = false;
obj = ptr.get();
// deleter disarmed - object not freed
}
And just for completeness (and to avoid a potential future broken link), here is the implementation of DisarmableDelete from http://paste.ubuntu.com/23866812/.
template <typename T, typename Deleter = typename std::default_delete<T> >
struct DisarmableDelete : private Deleter {
void operator()(T* ptr) { if(_armed) Deleter::operator()(ptr); }
bool _armed = true;
};
It depends on who "owns" the pointer, once it has been exposed to the 'outside world.' Ownership essentially boils down to: "who is responsible for freeing this memory, later?"
It can be answered with a simple question: when MyFunc is called, is the caller responsible for deleting the pointer when it's done?
If so, then MyFunc needs to 'release' the ownership, otherwise the shared_ptr will automatically delete the pointer, when it goes out of scope. This actually can't be done, using shared_ptr. You need to use a unique_ptr instead, and call unique_ptr::release().
If not - if MyFunc will simply use the resulting pointer and forget about it without delete-ing it - then you can simply return the 'raw' pointer using shared_ptr::get(). You must be careful, because this implies that the shared_ptr still exists elsewhere in your code.
I can see four alternatives, as highlighted below. They are all horrible, and short of switching your ownership to std::unique_ptr<T> and returning via obj = ptr.release(); I can only offer a hack where the argument is assigned to the pointer upon destruction, but you still need to catch the exception and test whether the pointer was assigned.
#include <iostream>
#include <memory>
#include <exception>
struct foo {
void bar() const { std::cout << this << " foo::bar()\n"; }
~foo() { std::cout << this << " deleted\n"; }
};
void f1(foo*& obj) {
obj = new foo;
// do stuff... if an exception is thrown before we return we are
// left with a memory leak
}
void f2(foo*& obj) {
auto holder = std::make_shared<foo>();
// do stuff.. if an exception is thrown the pointer will be
// correclty deleted.
obj = holder.get(); // awesome, I have a raw pointer!
} // oops, the destructor gets called because holder went out of
// scope... my pointer points to a deleted object.
void f3(foo*& obj) {
auto holder = std::make_unique<foo>();
// do stuff.. if an exception is thrown the pointer will be
// correclty deleted.
obj = holder.release(); // awesome, I have a raw pointer!
} // no problem whem holder goes out of scope because it does not own the pointer
void f4(foo*& obj) {
// a super-weird hack that assigns obj upon deletion
std::shared_ptr<foo> holder(new foo, [&obj](foo*& p){ obj = p; });
throw std::exception();
} // no problem whem holder goes out of scope because it does not own
// the pointer... but if an execption is throw we need to delete obj
int main() {
foo* p1;
f1(p1);
p1->bar();
foo* p2;
f2(p2);
// p2->bar(); // error
foo* p3;
f3(p3);
p3->bar();
foo* p4;
try {
f4(p4);
} catch(...) {
std::cout << "caught an exception... test whether p4 was assigned it\n";
}
p4->bar(); // I still need to delete this thing
}
The point of shared_ptr is to express shared ownership.
Anything that does not share in the ownership of an object -- that doesn't have the right to make an object lifetime last longer, and the object lifetime is the union of the shared owners request for object lifetime -- should not use a shared_ptr.
Here, you have a shared_ptr and you are going to return it. At that point, you are violating the assumptions of shared_ptr; anyone who kept a shared_ptr copy expects that its content can last as long as it requests.
Meanwhile, the calling code thinks it owns the MyObject* raw pointer you passed it.
This is an example of misuse of shared_ptr.
Saying "we have memory management issues, use shared_ptr" doesn't fix memory management issues. Proper use of shared_ptr requires care and design, and the design must be that when the end of the lifetime of the object in question is shared by 2 or more pieces of data and/or code.
The internal code, if it does not own the pointer, should use either something like an observer_ptr<T> or a raw T* (the first to make it clear it doesn't own the object).
Ownership should be explicit, and in a unique_ptr. It can then call .release() to pass ownership to a raw pointer if required; in practice, I would change your signature to take a unique_ptr&, or have it return a unique_ptr.
The caller would then call .release() when they want to use some other object lifetime management system, or that object lifetime management system should consume unique_ptrs (thus being extremely clear about taking ownership of things).
Use a non-hack solution.
Such as a std::shared_ptr<std::unique_ptr<T>>. In this case, you have shared ownership of a unique ownership.
The unique_ptr can have its ownership taken from it (via .release()). When it does so, all of the shared_ptrs that still exist will have their unique_ptr also be cleared.
This places the shared unique ownership front and center, instead of hacking a non-deleter into a shared_ptr and having dangling shared_ptrs that think they have ownership over data but do not.
The best approach is to use internally unique_ptr and call its .release() method before returning the raw pointer.
If you are stuck to use shared_ptr internally, an option is to create it specifying a custom, "noop" deleter, that just does nothing when shared_ptr is destroyed (instead of calling delete on the owned pointer). The get the raw pointer from shared_ptr as usual (.get() method).
An example of such a deleter can be found in the Boost library (null_deleter).
But please note that doing this effectively "disable" the usefulness of having a shared_ptr at all...
If your managing the lifespan of the allocated object internally with std::shared_ptr, and are returning a raw pointer for access and don't want this pointer to affect the ref count, you can return the raw pointer by calling shared_ptr.get().
It can be problematic to return smart pointers if your using a tool like Swig to generate wrappers for other languages.

Return by reference in C++ - Reference assignment vs value assignment

Suppose I have:
class SomeObject {
};
SomeObject& f() {
SomeObject *s = new SomeObject();
return *s;
}
// Variant 1
int main() {
SomeObject& s = f();
// Do something with s
}
// Variant 2
int main() {
SomeObject s = f();
// Do something with s
}
Is there any difference between the first variant and the second? any cases I would use one over the other?
Edit: One more question, what does s contain in both cases?
First, you never want to return a reference to an object which
was dynamically allocated in the function. This is a memory
leak waiting to happen.
Beyond that, it depends on the semantics of the object, and what
you are doing. Using the reference (variant 1) allows
modification of the object it refers to, so that some other
function will see the modified value. Declaring a value
(variant 2) means that you have your own local copy, and any
modifications, etc. will be to it, and not to the object
referred to in the function return.
Typically, if a function returns a reference to a non-const,
it's because it expects the value to be modified; a typical
example would be something like std::vector<>::operator[],
where an expression like:
v[i] = 42;
is expected to modify the element in the vector. If this is
not the case, then the function should return a value, not
a reference (and you should almost never use such a function to
initialize a local reference). And of course, this only makes
sense if you return a reference to something that is accessible
elsewhere; either a global variable or (far more likely) data
owned by the class of which the function is a member.
In the first variant you attach a reference directly to a dynamically allocated object. This is a rather unorthodox way to own dynamic memory (a pointer would be better suited for that purpose), but still it gives you the opportunity to properly deallocate that object. I.e. at the end of your first main you can do
delete &s;
In the second variant you lose the reference, i.e. you lose the only link to that dynamically allocated object. The object becomes a memory leak.
Again, owning a dynamically allocated object through a reference does not strike me as a good practice. It is usually better to use a pointer or a smart pointer for that purpose. For that reason, both of your variants are flawed, even though the first one is formally redeemable.
Variant 1 will copy the address of the object and will be fast
Variant 2 will copy the whole object and will be slow (as already pointed out in Variant2 you cant delete the object which you created by calling new)
for the edit: Both f contain the same Object
None of the two options you asked about is very good. In this particular case you should use shared_ptr or unique_ptr, or auto_ptr if you use older C++ compilers, and change the function so it returns pointer, not reference. Another good option is returning the object by value, especially if the object is small and cheap to construct.
Modification to return the object by value:
SomeObject f() { return SomeObject(); }
SomeObject s(f());
Simple, clean, safe - no memory leaking here.
Using unique_ptr:
SomeObject* f() { return new SomeObject(); }
unique_ptr<SomeObject> s(f());
One of the advantages of using a unique_ptr or shared_ptr here is that you can change your function f at some point to return objects of a class derived from SomeObject and none of your client code will need to be changed - just make sure the base class (SomeObject) has a virtual constructor.
Why the options you were considering are not very good:
Variant 1:
SomeObject& s = f();
How are you going to destroy the object? You will need address of the object to call it's destructor anyway, so at some point you would need to dereference the object that s refers to (&s)
Variant 2. You have a leak here and not a chance to call destructor of the object returned from your function.

Delete an object with a protected destructor

I have to write a shared pointer for class, and among many other things that it has to do is make sure it can delete the object that it is pointing to.
How can I code a solution that will work with an object that has a protected destructor?
Additionally, if the object was created using placement new, I should not be calling delete on the the object, as that space may still be in use (will the delete call even work?). How can I detect such cases?
The relevant bits of the spec:
void reset(); The smart pointer is set to point to the null pointer. The reference count for the currently pointed to object, if any, is decremented.
Sptr(); Constructs a smart pointer that points to the null pointer.
template <typename U>
Sptr(U *); Constructs a smart pointer that points to the given object. The reference count is initialized to one.
Sptr(const Sptr &);
template <typename U> Sptr(const Sptr<U> &);
The reference count is incremented. If U * is not implicitly convertible to T *, this will result in a syntax error. Note that both the normal copy constructur and a member template copy constructor must be provided for proper operation.
The way the code is called:
Sptr<Derived> sp(new Derived);
char *buf = (char *) ::operator new(sizeof(Sptr<Base1>));
Sptr<Base1> &sp2 = *(new (buf) Sptr<Base1>());
sp2 = sp;
sp2 = sp2;
sp.reset();
sp2.reset();
::operator delete(buf);
Base1 has everything protected.
The whole point of making the destructor non-public is to prevent the object from being arbitrarily destroyed. There's no good way to get around that. (Even if there is a general way, it's not a good way, as it would require breaking the hell out of encapsulation in order to do so.)
If you want an object to be destroyed by some class other than itself, make the destructor public. If you don't, then your pointer class won't be able to destroy the object either.
Alternatively, you could make the pointer class a friend of whatever classes you want it to work with. But that's ugly in a number of ways, not least of which is that it rather arbitrarily limits the valid types of objects you can use with it.
Along with the reference counter store a pointer to the function that will delete the object (a 'deleter'). You will instantiate the deleter in the templated constructor of the smart pointer, there you know the derived type. Here is an extremely naive pseudocode:
template<class T> void DefaultDeleter(void *p) { delete static_cast<T*>(p); }
struct ref_counter {
int refs;
void *p;
void (*d)(void *);
};
template<class T> class Sptr {
/* ... */
template <typename U> Sptr(U *p)
{
_c = new ref_counter;
_c->refs = 1;
_c->p = static_cast<void*>(p);
_c->d = &DefaultDeleter<U>;
_p = p;
}
T *_p;
ref_counter *_c;
};
When refs drops to zero, invoke (*_c->d)(_c->p) to destroy the pointed object.
I of course assume that the destructor of Base is protected and the one of Derived is public, as otherwise the exercise makes no sense.
Note: This is why std::shared_ptr can be safely used with base classes with a non-virtual destructor.
Your class could take a deleter functor that would then become responsible for deallocating the object. By doing this you'd dump the problem of access to the destructor onto whoever's using your class. :)
Joking aside, if the caller knows how to create instances of the class, they should also know how to destroy those instances.
This would also provide a way to solve the problems associated with placement new.
After reading your spec update I can tell you that there is no way of implementing that properly because:
There is no way for your pointer to distinguish between the new and placement new case. The only thing your deleter can do is call delete p. That is not the right thing to do in the placement new case although it might work in your example because the buffer happens to be the right size and allocated with new.
As explained in the other answers, there is no good way for your deleter to get around the protected destructor problem.
By adding the possibility of a custom deleter to the constructor you would solve both these problems.
Edit: This is how you would add a custom deleter:
template <typename U> Sptr(U *, std::function<void(T*)> &&deleter);
Constructs a smart pointer that points to the given object. The
reference count is initialized to one. The custom deleter is called
when the reference count reaches zero with the raw pointer to the
instance.

Check for non-deallocated pointer

Assume a pointer object is being allocated on one point and it is being returned to different nested functions. At one point, I want to de-allocate this pointer after checking whether it is valid or already de-allocated by someone.
Is there any guarantee that any of these will work?
if(ptr != NULL)
delete ptr;
OR
if(ptr)
delete ptr;
This code does not work. It always gives Segmentation Fault
#include <iostream>
class A
{
public:
int x;
A(int a){ x=a;}
~A()
{
if(this || this != NULL)
delete this;
}
};
int main()
{
A *a = new A(3);
delete a;
a=NULL;
}
EDIT
Whenever we talk about pointers, people start asking, why not use Smart Pointers.
Just because smart pointers are there, everyone cannot use it.
We may be working on systems which use old style pointers. We cannot convert all of them to smart pointers, one fine day.
if(ptr != NULL) delete ptr;
OR
if(ptr) delete ptr;
The two are actually equivalent, and also the same as delete ptr;, because calling delete on a NULL pointer is guaranteed to work (as in, it does nothing).
And they are not guaranteed to work if ptr is a dangling pointer.
Meaning:
int* x = new int;
int* ptr = x;
//ptr and x point to the same location
delete x;
//x is deleted, but ptr still points to the same location
x = NULL;
//even if x is set to NULL, ptr is not changed
if (ptr) //this is true
delete ptr; //this invokes undefined behavior
In your specific code, you get the exception because you call delete this in the destructor, which in turn calls the destructor again. Since this is never NULL, you'll get a STACK OVERFLOW because the destructor will go uncontrollably recursive.
Do not call delete this in the destructor:
5.3.5, Delete: If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will
invoke the destructor (if any) for the object or the elements of the array being deleted.
Therefore, you will have infinite recursion inside the destructor.
Then:
if (p)
delete p;
the check for p being not null (if (x) in C++ means if x != 0) is superfluous. delete does that check already.
This would be valid:
class Foo {
public:
Foo () : p(0) {}
~Foo() { delete p; }
private:
int *p;
// Handcrafting copy assignment for classes that store
// pointers is seriously non-trivial, so forbid copying:
Foo (Foo const&) = delete;
Foo& operator= (Foo const &) = delete;
};
Do not assume any builtin type, like int, float or pointer to something, to be initialized automatically, therefore, do not assume them to be 0 when not explicitly initializing them (only global variables will be zero-initialized):
8.5 Initializers: If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an
object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized
So: Always initialize builtin types!
My question is how should I avoid double delete of a pointer and prevent crash.
Destructors are ought to be entered and left exactly once. Not zero times, not two times, once.
And if you have multiple places that can reach the pointer, but are unsure about when you are allowed to delete, i.e. if you find yourself bookkeeping, use a more trivial algorithm, more trivial rules, or smart-pointers, like std::shared_ptr or std::unique_ptr:
class Foo {
public:
Foo (std::shared_ptr<int> tehInt) : tehInt_(tehInt) {}
private:
std::shared_ptr<int> tehInt_;
};
int main() {
std::shared_ptr<int> tehInt;
Foo foo (tehInt);
}
You cannot assume that the pointer will be set to NULL after someone has deleted it. This is certainly the case with embarcadero C++ Builder XE. It may get set to NULL afterwards but do not use the fact that it is not to allow your code to delete it again.
You ask: "At one point, I want to de-allocate this pointer after checking whether it is valid or already de-allocated by someone."
There is no portable way in C/C++ to check if a >naked pointer< is valid or not. That's it. End of story right there. You can't do it. Again: only if you use a naked, or C-style pointer. There are other kinds of pointers that don't have that issue, so why don't use them instead!
Now the question becomes: why the heck do you insist that you should use naked pointers? Don't use naked pointers, use std::shared_ptr and std::weak_ptr appropriately, and you won't even need to worry about deleting anything. It'll get deleted automatically when the last pointer goes out of scope. Below is an example.
The example code shows that there are two object instances allocated on the heap: an integer, and a Holder. As test() returns, the returned std::auto_ptr<Holder> is not used by the caller main(). Thus the pointer gets destructed, thus deleting the instance of the Holder class. As the instance is destructed, it destructs the pointer to the instance of the integer -- the second of the two pointers that point at that integer. Then myInt gets destructed as well, and thus the last pointer alive to the integer is destroyed, and the memory is freed. Automagically and without worries.
class Holder {
std::auto_ptr<int> data;
public:
Holder(const std::auto_ptr<int> & d) : data(d) {}
}
std::auto_ptr<Holder> test() {
std::auto_ptr<int> myInt = new int;
std::auto_ptr<Holder> myHolder = new Holder(myInt);
return myHolder;
}
int main(int, char**) {
test(); // notice we don't do any deallocations!
}
Simply don't use naked pointers in C++, there's no good reason for it. It only lets you shoot yourself in the foot. Multiple times. With abandon ;)
The rough guidelines for smart pointers go as follows:
std::auto_ptr -- use when the scope is the sole owner of an object, and the lifetime of the object ends when the scope dies. Thus, if auto_ptr is a class member, it must make sense that the pointed-to object gets deletes when the instance of the class gets destroyed. Same goes for using it as an automatic variable in a function. In all other cases, don't use it.
std::shared_ptr -- its use implies ownership, potentially shared among multiple pointers. The pointed-to object's lifetime ends when the last pointer to it gets destroyed. Makes managing lifetime of objects quite trivial, but beware of circular references. If Class1 owns an instance of Class2, and that very same instance of Class2 owns the former instance of Class1, then the pointers themselves won't ever delete the classes.
std::weak_ptr -- its use implies non-ownership. It cannot be used directly, but has to be converted back to a shared_ptr before use. A weak_ptr will not prevent an object from being destroyed, so it doesn't present circular reference issues. It is otherwise safe in that you can't use it if it's dangling. It will assert or present you with a null pointer, causing an immediate segfault. Using dangling pointers is way worse, because they often appear to work.
That's in fact the main benefit of weak_ptr: with a naked C-style pointer, you'll never know if someone has deleted the object or not. A weak_ptr knows when the last shared_ptr went out of scope, and it will prevent you from using the object. You can even ask it whether it's still valid: the expired() method returns true if the object was deleted.
You should never use delete this. For two reasons, the destructor is in the process of deleting the memory and is giving you the opportunity to tidy up (release OS resources, do a delete any pointers in the object that the object has created). Secondly, the object may be on the stack.

c++ must delete a references?

in the following code:
class x
{
private:
someRef& m_ref;
public:
x(someRef& someRef):m_ref(someRef)
{
}
do I need to do:
~x()
{
delete m_ref;
}
which by the way doesnt work without getting the pointer...
basically I'm asking: Do I need to call a destructor on a reference member?
No.
You only need to delete an object if you own it. If you were passed a reference, it means that someone else owns it, thus it's unnecessary and thankfully the language prevents it.
I don't think one actually strictly speaking ever deletes even pointers. What you delete are dynamically allocated objects (or arrays of objects) that the pointer is a handle for. If the object originates from a call to new and it is the responsibility of this class to clean up after this object, then you call delete.
It is technically possible that a reference might be referring to a dynamically allocated object:
int main()
{
//in principle a reference can also refer to a dynamically allocated object
x var(*new someRef);
}
//and if that is the intended usage:
x::~x()
{
delete &m_ref;
}
However, this would be incredibly bad style. By convention, the "owning" handle of a dynamically allocated object should not be a reference.
No. You can only delete pointers, not references, and even then you must only delete objects that you allocated using the new operator. And then you must be sure to delete them only once. Here is the case in which you would need to use delete in your destructor:
class x
{
private:
someObj* m_ptr;
public:
x():m_ptr(new someObj())
{
}
~x()
{
delete m_ptr;
}
But in general it's best to avoid even this and use smart pointers instead.
I want to clarify some misconceptions you seem to have that are beyond the intent of your question:
When a class's destructor is called all of it's members' destructors get called as well.
Calling delete is not the same as calling the destructor. delete explicitly calls the destructor and also calls operator delete at the objects location, it is a 2 part thing.
For a small bit of extra clarification I want to offer the following:
int *pi = new int;
//int& ir = pi; // can't do
// this a reference to the pointer but it is an error
// because or the type difference int& vs int* and
// static_cast won't help. reinterpret_cast would allow
// the assignment to take place but not help the 'delete ir'
int& ir = *pi; // this is OK - a reference to what the pointer points to.
// In other words, the the address of the int on the heap.
//delete ir; // can't do, it is a reference and you can't delete non-pointers.
delete &ir; // this works but it is still not "deleting a reference".
// The reference 'ir' is another name for the heap-based int.
// So, &ir is the address of that int, essentially a pointer.
// It is a pointer that is being used by delete, not a reference.