Indestructible pointer: Sample code for feedback

D

David Coppit

Hi all,

Most smart pointers remove the need to call delete. What I want is a
pointer that turns delete into a no-op for "unauthorized" clients.

The reason is that I'm generating code from pre-existing legacy code that
assumes the caller deletes the pointer. However, I want to reduce memory
copies for the case where there are multiple clients fetching a pointer to
the same memory.

Admittedly, this is a rare scenario, but I thought I'd share it with you
all for posterity and any feedback you can give me. (It seems like a bit
of a hack, especially if you notice that operator<< doesn't actually print
out the pointer.)

David

First, here's a toy program demonstrating the situation in the original code:

--------------%<---------------
#include <iostream>

using namespace std;

// Old code returned a copy for clients to delete
int* My_Library_To_Optimize()
{
static int *some_private_data = new int(10);

// Create a new copy for every caller, which is slow
return new int(*some_private_data);
}

int main()
{
int *p1 = My_Library_To_Optimize();

// This is the client code generated from legacy code. The client code
// can either delete the pointer, or pass it off to someone else to be
// deleted.
{
cout << *p1 << endl;
delete p1;
}

int *p2 = My_Library_To_Optimize();

// More client code
{
cout << *p2 << endl;
delete p2;
}

return 0;
}
--------------%<---------------

Here's the new code:

--------------%<---------------
#include <iostream>
#include "tough_ptr"

using namespace std;

// New code will return an indestructible pointer
tough_ptr<int>* My_Library_To_Optimize()
{
static int *some_private_data = new int(10);

// Copies of tough_ptr are cheaper
return new tough_ptr<int>(some_private_data);
}

int main()
{
tough_ptr<int> *p1 = My_Library_To_Optimize();

// Client code is unchanged! Delete does nothing!
{
cout << *p1 << endl;
delete p1;
}

tough_ptr<int> *p2 = My_Library_To_Optimize();

// Client code is unchanged! Delete does nothing!
{
cout << *p2 << endl;
delete p2;
}

return 0;
}
--------------%<---------------

Here's tough_ptr:

--------------%<---------------
#ifndef TOUGH_PTR_H
#define TOUGH_PTR_H

#include <iostream>

template<typename _Tp>
class tough_ptr
{
private:
_Tp* _M_ptr;

public:
explicit
tough_ptr(_Tp* __p = 0) throw() : _M_ptr(__p) { }

tough_ptr(tough_ptr& __a) throw() : _M_ptr(__a._M_ptr) { }

template<typename _Tp1>
tough_ptr(tough_ptr<_Tp1>& __a) throw() : _M_ptr(__a._M_ptr) { }

tough_ptr&
operator=(tough_ptr& __a) throw()
{
_M_ptr = __a._M_ptr;
return *this;
}

template<typename _Tp1>
tough_ptr&
operator=(tough_ptr<_Tp1>& __a) throw()
{
_M_ptr = __a._M_ptr;
return *this;
}

~tough_ptr() {}

const _Tp* get() const { return _M_ptr; }

_Tp&
operator*() const throw()
{
return *_M_ptr;
}

_Tp*
operator->() const throw()
{
return _M_ptr;
}

template<typename _Tp1>
operator tough_ptr<_Tp1>() throw()
{ return tough_ptr<_Tp1>(this->_M_ptr); }
};

template<typename _Tp>
std::eek:stream& operator<< (std::eek:stream& in_ostream, const tough_ptr<_Tp>&
in)
{
in_ostream << *(in.get());
return in_ostream;
}

#endif // TOUGH_PTR_H
--------------%<---------------
 
D

David Coppit

Here's an updated version with more realistic code. Also, it's more
efficient because the returned pointer is not created on-the-fly. Note the
override of operator delete in tough_ptr.

David

Old code:

-------------------%<-------------------
#include <iostream>

using namespace std;

class Data_Feed
{
public:
Data_Feed() {
}

~Data_Feed() {
}

void Set_Data(const int in_data) {
some_private_data = in_data;
}

// Old code returned a copy for clients to delete
int* Get_Data() {
return new int(some_private_data);
}

private:
int some_private_data;
};

int main()
{
Data_Feed feeder;

feeder.Set_Data(1);

int *p1 = feeder.Get_Data();

// This is the client code generated from legacy code. The client code
// can either delete the pointer, or pass it off to someone else to be
// deleted.
{
cout << *p1 << endl;
delete p1;
}

feeder.Set_Data(2);

int *p2 = feeder.Get_Data();

// More client code
{
cout << *p2 << endl;
delete p2;
}

return 0;
}
-------------------%<-------------------

New code:

-------------------%<-------------------
#include <iostream>
#include "tough_ptr"

using namespace std;

class Data_Feed
{
public:
Data_Feed() {
return_ptr = new tough_ptr<int>(&some_private_data);
}

~Data_Feed() {
::delete(return_ptr);
}

void Set_Data(const int in_data) {
some_private_data = in_data;
}

// New code will return an indestructible pointer
tough_ptr<int>*& Get_Data() {
return return_ptr;
}

private:
tough_ptr<int> *return_ptr;
int some_private_data;
};

int main()
{
Data_Feed feeder;

feeder.Set_Data(1);

tough_ptr<int> *p1 = feeder.Get_Data();

// Client code is unchanged! Delete does nothing!
{
cout << *p1 << endl;
delete p1;
}

feeder.Set_Data(2);

tough_ptr<int> *p2 = feeder.Get_Data();

// Client code is unchanged! Delete does nothing!
{
cout << *p2 << endl;
delete p2;
}

return 0;
}
-------------------%<-------------------

tough_ptr code:

-------------------%<-------------------
#ifndef TOUGH_PTR_H
#define TOUGH_PTR_H

#include <iostream>

template<typename _Tp>
class tough_ptr
{
private:
_Tp* _M_ptr;

public:
explicit
tough_ptr(_Tp* __p = 0) throw() : _M_ptr(__p) { }

tough_ptr(tough_ptr& __a) throw() : _M_ptr(__a._M_ptr) { }

template<typename _Tp1>
tough_ptr(tough_ptr<_Tp1>& __a) throw() : _M_ptr(__a._M_ptr) { }

tough_ptr&
operator=(tough_ptr& __a) throw()
{
_M_ptr = __a._M_ptr;
return *this;
}

template<typename _Tp1>
tough_ptr&
operator=(tough_ptr<_Tp1>& __a) throw()
{
_M_ptr = __a._M_ptr;
return *this;
}

~tough_ptr() {}

const _Tp* get() const { return _M_ptr; }

_Tp&
operator*() const throw()
{
return *_M_ptr;
}

_Tp*
operator->() const throw()
{
return _M_ptr;
}

template<typename _Tp1>
operator tough_ptr<_Tp1>() throw()
{ return tough_ptr<_Tp1>(this->_M_ptr); }

static void operator delete(void *p){}
};

template<typename _Tp>
std::eek:stream& operator<< (std::eek:stream& in_ostream, const tough_ptr<_Tp>& in)
{
in_ostream << *(in.get());
return in_ostream;
}

#endif // TOUGH_PTR_H
-------------------%<-------------------
 
A

Alf P. Steinbach

* David Coppit:
tough_ptr<int>* My_Library_To_Optimize()
{
static int *some_private_data = new int(10);

// Copies of tough_ptr are cheaper
return new tough_ptr<int>(some_private_data);
}

int main()
{
tough_ptr<int> *p1 = My_Library_To_Optimize();

// Client code is unchanged! Delete does nothing!
{
cout << *p1 << endl;
delete p1;
}

tough_ptr<int> *p2 = My_Library_To_Optimize();

// Client code is unchanged! Delete does nothing!
{
cout << *p2 << endl;
delete p2;
}

return 0;
}

How is this different from


#include <iostream>
using namespace std;

typedef int** IntPtrPtr;

IntPtrPtr My_Library_To_Optimize()
{
static int *some_private_data = new int(10);
return new IntPtrPtr(some_private_data);
}

int main()
{
IntPtrPtr p1 = My_Library_To_Optimize();

cout << *p1 << endl;
delete p1;

IntPtrPtr p2 = My_Library_To_Optimize();

cout << *p2 << endl;
delete p2;

return 0;
}
 
D

David Coppit

How is this different from

I couldn't get your code to compile. I did a pointer to tough_ptr so that
its class delete would be called. Unfortunately, this approach doesn't
work in general, because you can't do things like ptr->begin() for STL
data types.

In hindsight, a better way might be to override global operator delete.
That way I can register plain pointers to be protected in some global data
store, and check it before deleting.

Unfortunately, I can't seem to get it to work right using g++ 3.3. Below
is sample code.

David

Here is the test program:

-------------%<------------------
#include <string>
#include <iostream>

using namespace std;

#include "protected_pointer.h"

int main()
{
string* indestructible = new string("testing");
protect_pointer( indestructible );

cout << "Before attempted delete: " << *indestructible << endl;

// Does nothing in operator delete
delete indestructible;

// Prints out junk and hangs (?!)
cout << "After attempted delete: " << *indestructible << endl;

unprotect_pointer( indestructible );
delete indestructible;

return 0;
}
-------------%<------------------

Here is the implementation of operator delete:

-------------%<------------------
#include <set>

#include "protected_pointer.h"

std::set< void* > undeletable_pointers;

void protect_pointer(void *a_pointer)
{
undeletable_pointers.insert(a_pointer);
}

void unprotect_pointer(void *a_pointer)
{
undeletable_pointers.erase(a_pointer);
}

void operator delete(void *a_pointer) throw()
{
if (undeletable_pointers.find(a_pointer) == undeletable_pointers.end())
free(a_pointer);
}
-------------%<------------------

Here is the header:

-------------%<------------------
#ifndef PROTECTED_POINTER_H
#define PROTECTED_POINTER_H

void protect_pointer(void *a_pointer);
void unprotect_pointer(void *a_pointer);

void operator delete(void *a_pointer) throw();

#endif // PROTECTED_POINTER_H
-------------%<------------------
 
D

David Coppit

Replace 'new IntPtrPtr' with 'new int*'.

How is your code different?

Well, for one,

cout << *p1 << endl;

prints "10" not a pointer location. My goal is to have something that
looks and acts like a pointer to the client, but can't be deleted.

I investigated overloading global delete, but then realized that I can't
stop the destructor of a class from being called (although I can stop the
memory from being deallocated afterwards). So in cases where I'm trying to
protect an arbitrary pointer, this method won't work.

So I guess it's back to the tough_ptr method I was trying to get to work
earlier.

David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top