Scott Meyers

Comments on Item 27 of More Effective C++

Last Updated December 15, 1999by Scott Meyers

The messages below make up correspondance related to More Effective
C++'s discussion of how to determine whether an object is on the heap.
I've edited out unnecessary email headers and omitted parts of the messages
not germane to the issue at hand, but other than that, these are verbatim
messages sent to/from me on this topic. I'm grateful to each of the
authors for their permission to make their messages available for general
viewing.

After Ron van der Wal and I exchanged email, Ron put up a web page summarizing
his extensive and insightful thoughts on this topic. That page contains
essentially the same information as is shown in our correspondance below,
but he's formatted his page, so it's easier to read. If you read his page,
however, please don't forget to come back here to read the other authors'
comments, because they describe interesting approaches to this problem that
are different from both mine and Ron's.

From: Ron van der Wal
Date: Mon, 26 Feb 96 10:44:40 PST
Subject: On the heap checks
To: Scott Meyers
Thank you for another well written and useful book on C++ - meaning of course
"More effective C++". I would like to comment on the central issue of Item
27, i.e. ways to determine whether or not some object is allocated off the
heap rather than, say, stack or static data space. Unless noted otherwise, my
comments refer to the UPNumber and HeapTracked samples that you use
throughout Item 27.
1. A static bool 'onTheHeap' is not satisfactory (pp. 148-149)
--------------------------------------------------------------
This is true. There is, however, a fairly simple way to circumvent some of
the problems you outline: replace the bool flag by a counter. For example:
class UPNumber {
public:
...as before...
private:
static int heapPending; // Replaces 'onTheHeap'
};
int UPNumber::heapPending = 0;
void *UPNumber::operator new(size_t sz)
{
++heapPending;
return ::operator new(sz);
}
UPNumber::UPNumber()
{
if (heapPending < 1)
throw HeapConstraintViolation();
...
--heapPending;
}
This solves most of the problems you mention (we'll get to array allocations
in a moment), including the new UPNumber(*new UPNumber()) situation. In
fact, it could only fail (as far as single objects are concerned) in the
following situation:
new UPNumber(UPNumber());
This fails if (and only if) the compiler happens to generate code as follows:
1. Call operator new
2. Call the constructor of the temporary
3. Call the constructor of the dynamic object
The order of 2 and 3 will never be reversed, of course, so the question is
simply whether or not the operator new call will precede the construction of
the temporary. (This scenario would also cause the 'onTheHeap' approach to
fail.) Any other combination of constructors and/or operator new invocations
will be safe with the counter approach. In the example above it remains to
be seen if the use of a temporary makes sense, but it's legal anyway so we
have to deal with it.
As to arrays of UPNumbers, this could be solved by also overloading operator
new[] for the UPNumber class and implementing it as follows:
void *UPNumber::operator new[](size sz)
{
heapPending += sz / sizeof(UPNumber);
return ::operator new[](sz);
}
This solves the new UPNumber[100] you mention, assuming that 'sz' is indeed
the correct multiple of sizeof(UPNumber). When will this not be the case? I
can see two possibilities:
1. When the C++ implementation under consideration needs some extra space
in the array for bookkeeping, and to this end increases the passed-in
'sz'. This is a possibility allowed by the ANSI/ISO C++ draft standard
(see section 5.3.4), but it did not occur with the C++ compilers I tried
(Borland, Microsoft, Symantec). It may with others, though.
2. When UPNumber::operator new[] is inherited and used by a class derived
from UPNumber *and* sizeof(Derived) > sizeof(UPNumber). There is no
foolproof way to check for this (in particular, the size of the derived
class may itself be an integral multiple of the size of UPNumber), so
this will cause problems too.
To summarize: The counter approach does not completely solve the problems you
outlined on pp. 148-149, but it improves on the bool flag approach. In fact,
it still is a cheap solution and it may suffice for a large number of
situations, especially if portability is not an issue or will be considered
as the need arises (as frequently happens :-).
2. Tracking heap based objects (pp. 154-156)
--------------------------------------------
Your HeapTracked class does indeed exactly that in a portable and bullet
proof way. It is, however, a fairly expensive solution both in space and
time. In particular, the list template will often (if not always)
be implemented as a doubly-linked list and therefore require an allocation of
a list element per address stored. This may be a quite considerable overhead,
both in space and time. Furthermore, HeapTracked::isOnHeap() operates in
linear time, and this too could be a problem.
I have two alternatives to offer. Neither is as generally applicable or as
foolproof as your solution, but they are a lot cheaper (requiring only a
single integer per tracked object, and checking in constant time) and could
be appropriate in many situations. After all, if you really need to track
heap based objects, you may also be willing to impose some constraints on the
use of the tracked class. (I have done so anyway.)
Both methods add a signature to the allocated object, which is initialized by
the overloaded operator new for the class (although the location of the
signature differs between the two approaches). The signature is not
initialized if the object is constructed otherwise, and this is also the
possibly fatal flaw: a correct signature may be accidentally present even in
objects that do not come off the heap. I'll return to that later.
A. Prepend a signature to the allocated block
---------------------------------------------
This first solution would work out as follows: overload operator new for the
class and have it allocate some extra space before the actual block, then
fill this with a special signature. For example:
class HeapTracked {
public:
...as before (operator delete not needed)...
private:
static const int32 signature = 0x57647652L;
static bool checkSignature(const void *);
...RawAddress stuff deleted...
};
void *HeapTracked::operator new(size_t sz)
{
// Increase requested size by signature size
sz += sizeof(int32);
// Get somewhat larger block
void *block = ::operator new(sz);
// Initialize heap signature, then return pointer to subblock
// beyond signature. (Second static_cast not strictly necessary.)
int32 *signptr = static_cast(block);
*signptr = signature;
return static_cast(signptr + 1);
}
bool HeapTracked::isOnHeap() const
{
return checkSignature(dynamic_cast(this));
}
bool HeapTracked::checkSignature(const void *ptr)
{
// Cast back to int32 pointer
const int32 *signptr = static_cast(ptr);
// Check signature in front of the block.
// ***This is the problem area, see text***
return signptr[-1] == signature;
}
(By the way, 'int32' is a typedef to ensure I have a 4-byte integer
regardless of my compiler's preferences.)
As you've probably spotted, the problem with this approach arises when
HeapTracked::checkSignature() attempts to retrieve the signature field that
precedes the actual object. In general, the address some_array[-1] is not
guaranteed to exist. If the block was allocated by HeapTracked::operator new,
it will exist, but we're just about to find that out and can't be sure yet.
This problem cannot be solved in a portable way, as far as I know. It may
just so happen that the address signptr[-1] is not valid; it will depend on
the platform at hand whether or not this is likely to occur. In my practice
(DOS, Win16, Win32, OS/2 and Mac apps), the most threatening situations are
in 16-bit segmented DOS/Win16 programs when signptr[-1] would cause segment
'underwrap' (this can be checked, albeit in a non-portable way) and in
others, when the passed-in block pointer actually addresses something on the
stack exactly at the bottom of a stack page, and signptr[-1] causes a page
fault on the stack's guard page. The latter condition is almost impossible to
check for, but it has never occurred to me (yet). Anyway, this method is not
without its problems, although it is a generally applicable as your
list approach in all other respects (inheritance, mix-in use,
etc.).
Finally, it does not work with arrays of heap tracked objects, but you wisely
ignored that subject too :-).
B. Include a signature in the object itself
-------------------------------------------
This second solution is one you'll probably balk at, but it still has its
merits and I've used it with success on several occasions (as I did with the
previous method). The idea is to place the signature field inside the
allocated object, yet still initialize it in HeapTracked::operator new. For
example:
class HeapTracked {
public:
...as before (operator delete not needed)...
private:
static const int32 signature = 0x57647652L;
int32 signfield; // Added
...RawAddress stuff deleted...
};
void *HeapTracked::operator new(size_t sz)
{
// Get block of correct size
void *block = ::operator new(sz);
// Point into the object-to-be and set its signature...
HeapTracked *htptr = static_cast(block);
htptr->signfield = signature;
return block;
}
bool HeapTracked::isOnHeap() const
{
return signfield == signature;
}
The HeapTracked constructors must leave this field alone of course, as must
the assignment operator for the class, so it's probably a good idea to define
at least the copy constructor and assignment operator for this class and
implement them to that effect.
If you're really into it, you might even want to implement
HeapTracked::operator new[] and step through the array of objects-to-be,
setting all their signature fields. In for a penny, in for a pound, don't you
think?
When is this method not applicable? In essence, when class HeapTracked is
used as a base class (e.g. as the mix-in you mention) and it is either not
the first base class, or it is a virtual base class. In both cases, the
start of the new object will not coincide with the start of the allocated
block, and the htptr->signfield reference will be off. The same applies if
the C++ implementation under consideration adds some bookkkeeping space in
front of the actual object (again allowed by ANSI/ISO C++). It's up to the
client to decide whether or not these restrictions are acceptable.
Accidental signature match
--------------------------
As I hinted at above, there still remains the problem of accidental signature
matches. Both method A and B assume that if the signature value matches the
predefined constant, we have a valid heap block. This may not always be true,
however, especially not so because in the case of not-heap-based objects, the
signature fields aren't initialized at all and may contain any value
(including the signature value). What can we do about it? Nothing absolutely
foolproof, but a few things might raise the confidence level enough to make
either method usable.
1. Use an 'improbable' signature value. Clearly, 0 would be a poor
choice, as would be 0xFFFFFFFF. I normally use a 4-byte value that does
not match anything systematic. In the above examples the value
0x57647652L was used; I find it nicely conspicuous in dumps, yet fairly
unlikely to occur by chance (try interpreting it as 4 ASCII characters).
If necessary, you can make the signature value less probable by
increasing its size (what about a 128-bit GUID?), but some compromise is
probably called for.
2. Clear the signature field in HeapTracked::operator delete (method
A and B) or in the HeapTracked destructor (only method B). This might
help a bit, but not much, since we're mainly interested in distinguishing
heap based objects from others, and not one heap based object from
another. Still, this could reduce the chance of accidents in method B if
we do it in the HeapTracked destructor for every HeapTracked object.
3. Use additional information above and beyond the signature field. For
example, add a signature field at the end of the object field as well
(this limits the application of the methods). Or, prepend another field
with a dynamically magical value, say the address of the object itself.
Or prepend an additional counter field and XOR the signature field with
that. All these methods would still allow HeapTracked::isOnHeap() to
operate in constant time, and don't require auxiliary data structures.
C. Dig into your C++ compiler
-----------------------------
One final method to get what you want (i.e. knowledge about heap-basedness)
is to consult your compiler's runtime implementation details. This is
extremely nonportable (it might even change from one compiler release to the
next), but what gives if you're desparate?
Many compilers will use some hidden information passed to the constructors
and destructors they generate, indicating whether or not the constructor
should call operator new before proceeding (effectively combining the
constructor call with the operator new call), and conversely, whether the
destructor should call operator delete when it's done. They will also add
some information to memory blocks indicating how many objects are in the
block (to take care of vector construction and destruction). If you can get
at these and are prepared to face the maintenance problems it causes, this
could also solve your problems.
Obviously, this approach is off-limits for a book like "More effective C++"
and I have never seen it being used by anybody but the compiler manufacturer
himself, but...
Date: Fri, 01 Mar 1996 21:40:24 EST
From: Scott Meyers
To: Ron van der Wal
Subject: Re: On the heap checks
Thanks for your thoughtful comments on checking to see if objects are on
the heap.
>1. A static bool 'onTheHeap' is not satisfactory (pp. 148-149)
>--------------------------------------------------------------
>This is true. There is, however, a fairly simple way to circumvent some of
>the problems you outline: replace the bool flag by a counter. For example:
I considered adding this to the book, but I decided against it. The
book is longer than I wanted it to be anyway, and my experience has
been that anybody who can figure out the onTheHeap bool trick can
pretty easily extend it to the counter idea. In retrospect, perhaps I
should have added it anyway, but if I did that in all the places where
there were logical extensions (especially reference counting), I'd have
a 400-page book instead of a 300-page book. (I wanted a 200-page
book!)
>Your HeapTracked class does indeed exactly that in a portable and bullet
>proof way. It is, however, a fairly expensive solution both in space and
>time. In particular, the list template will often (if not always)
>be implemented as a doubly-linked list and therefore require an allocation of
>a list element per address stored. This may be a quite considerable overhead,
>both in space and time. Furthermore, HeapTracked::isOnHeap() operates in
>linear time, and this too could be a problem.
True. However, I assume that the problem of optimizing the data
structure storing/finding addresses is independent of the
heap-or-no-heap problem I consider in the book.
> void *HeapTracked::operator new(size_t sz)
> {
> // Increase requested size by signature size
> sz += sizeof(int32);
>
> // Get somewhat larger block
> void *block = ::operator new(sz);
>
> // Initialize heap signature, then return pointer to subblock
> // beyond signature. (Second static_cast not strictly necessary.)
> int32 *signptr = static_cast(block);
> *signptr = signature;
> return static_cast(signptr + 1);
> }
There is another potential problem with this idea, and that's that you
may violate an alignment restriction. operator new is required to
return an address that satisfies alignment requirements for all types,
and this implementation assumes that 4-byte boundaries satisfies that.
Whether this is a problem in practice, I don't know. I've heard that on
some machines, doubles must be double-word aligned.
From: Ron van der Wal
Date: Sat, 2 Mar 96 14:04:31 PST
Subject: Re: On the heap checks
To: Scott Meyers
Yes, you're right and I should have mentioned it, because one of those
machines is the DEC Alpha which I occasionally use. It might even require
8-byte alignment for other types, since its long int is 64 bits (as is
that of the Sun Sparc).
From: Luke Tomasello
To: Scott Meyers
Subject: Heap-based objects
Date: Wed, 22 Apr 1998 16:34:28 -0700
I have been enjoying your book MEC++ and have what I believe is a
reasonable solution to the "is it on the heap" OR "Is it on the stack"
problem (Item 27).
I believe my solution is both elegant and portable.
The basic idea is to:
1. implement both new and delete for the class in question.
2. You then use malloc() in 'new' to allocate the memory.
3. Now initialize all of the memory to some known value. E.g.. "ALLOCATED ON
HEAP"
4. Your class will contain a char array of say 16 bytes. E.g.. TestBuf[16];
5. In the constructor you will check TestBuf for a match with our known
initialization value.
(The string probably won't match-up exactly, so you will need write a small
bit of code.)
Your ctor can now have logic something like:
myclass::myclass()
{
// set alloc state for this class instance
if (match(TestBuff,"ALLOCATED ON HEAP") == true)
allocatedOnHeap=true;
else
allocatedOnHeap=false;
}
I like using a string for the initialization value because it is eaiser to
match and does not suffer any alignment issues.
What do you think?
Date: Wed Apr 22 21:11:02 1998
To: Luke Tomasello
From: Scott Meyers
Subject: Re: Heap-based objects
This will often work, but it may fail under the following conditions:
- The address of TestBuff fails to be the same as the address returned
from operator new. This can happen if (1) multiple inheritance and/or
virtual inheritance is being used, or (2) the address returned from the
new operator isn't the same as the address returned from operator new.
(The C++ standard makes no guarantee that they are the same, though I
don't know of any implementations where they're not.)
- By chance, the memory for a non-heap object contains the byte string
you test for. For an object of size 16 bytes or larger, the chances of
this are remote, but as objects get smaller, the chances of this
happening increases.
I still like the solution I describe in the book, because it's portable, it
can never fail, and I believe it will offer acceptable performance most of
the time.
From: Luke Tomasello
To: Scott Meyers
Subject: RE: Heap-based objects
Date: Thu, 23 Apr 1998 09:36:22 -0700
What I meant in my original message is that when you initialize the memory
returned from malloc, you initialize the whole memory block and not simply
the memory occupied by TestBuf (doing so would cause the problems you
mention above.)
I think this only leaves your second point which we both agree is extremely
unlikely if a buffer of say 16 bytes is used.
Date: Thu Apr 23 10:15:10 1998
To: Luke Tomasello
From: Scott Meyers
Subject: RE: Heap-based objects
>I think this only leaves your second point which we both agree is extremely
>unlikely if a buffer of say 16 bytes is used.
Right, though it would be interesting to characterize the cases where your
solution would be preferable to the one I published. My solution may use
more total memory, but yours makes objects larger, so my guess is the
performance of the different approaches would depend on how often queries
were made to find out if an object was on the heap.
Is there a particular reason you like your solution better?
From: Luke Tomasello
To: Scott Meyers
Subject: RE: Heap-based objects
Date: Thu, 23 Apr 1998 10:42:14 -0700
I think my solution may be better:
1. if hundreds of objects are getting allocated and objects are often
deleted. (The list in your solution would be long, so search times are
long.)
2. if the application is multi-threaded. (There is some problems with
your approach in a multi-threaded app accessing the 'address list'
unless more code is added to handle this.)
I think we could eliminate the 16 byte buffer and simply do something like:
threshold=some_number; // probably 10-20 is sufficient
magicString="SOME MAGIC STRING";
myClass::myClass()
{
// make sure this class is big enough to eliminate virtually
// all possibility of having the random value on the stack
assert(sizeof(*this) > threshold);
// now do the test
if (match(this,magicString,sizeof(*this)))
IsOnHeap=true;
else
IsOnHeap=false;
}
Date: Thu Apr 23 11:37:37 1998
To: Luke Tomasello
From: Scott Meyers
Subject: RE: Heap-based objects
>I think my solution may be better:
>1. if hundreds of objects are getting allocated and objects are often
> deleted. (The list in your solution would be long, so search times are
> long.)
Fair enough.
>2. if the application is multi-threaded. (There is some problems with
> your approach in a multi-threaded app accessing the 'address list'
> unless more code is added to handle this.)
Right. Access to the list would clearly have to be made thread-safe.
>I think we could eliminate the 16 byte buffer and simply do something like:
>threshold=some_number; // probably 10-20 is sufficient
>magicString="SOME MAGIC STRING";
>myClass::myClass()
>{
> // make sure this class is big enough to eliminate virtually
> // all possibility of having the random value on the stack
> assert(sizeof(*this) > threshold);
>
> // now do the test
> if (match(this,magicString,sizeof(*this)))
> IsOnHeap=true;
> else
> IsOnHeap=false;
>}
Frankly, you could probably just cast the first 4 bytes of memory to an
int and then put a magic value there. Working with ints is fast, and the
chances of static or stack memory happening to have a particular value is,
well, one in about four billion.
Subject: MEC++ Item 27 (require/prohib. heap) suggestions
Date: Tue, 20 Apr 1999 23:19:02 -0400
From: Eric Anderson
To: Scott Meyers
I just read through "Item 27: Requiring or prohibiting heap-based objects",
and I'd like to offer a few suggestions.
A. To improve the HeapTracked mixin class:
1. Add a new data member bool createdWithNew (or the equivalent),
initialized to false for all new HeapTracked objects.
2. In the body of the HeapTracked constructor, place code to search the
static "new" list for a match to the address of "this". If a match is
found, then createdWithNew is set to true for that object and the
address is immediately removed from the static "new" list.
3. In HeapTracked::isOnHeap, substitute a simple return of the state of the
local bool createdWithNew data member.
4. Eliminate the specialized operation delete function. (If necessary, use
some other function to make the class abstract.)
In comparison with the solution in MEC++, this should be more time and more
space efficient:
Time -- Returning a bool value is much more efficient than doing a search
through a sequential list of length N. This savings occurs with each use
of isOnHeap.
Plus, there is also a savings for each search that checks for removing and
address from the list. If this operation is deferred to deletion time,
that means the "new" list must contain entries for every undeleted object.
This could be hundreds, thousands or more, depending on the application.
Each such search must make an O(N) search of that potentially long list.
When the search is performed during construction with immediate removal,
then the absolute worst case would be bounded by the number of nested,
simultaneously-in-process constructions of HeapTracked objects. At worst,
this is likely to be only a few at any time, and usually this will only be
one object deep or else empty (if "new" was not used), i.e. a search
through a list of typical length <= 1.
Space -- The space required for the extra bool member is much less than
keeping a whole list entry in the static "new" list, which includes space
for each address to remember, plus the overhead of the list pointers that
tie the list together (e.g. two extra pointers, if it is doubly linked).
Early on, you show the inadequacy of a simplistic solution that uses a
specialized "operator new" to toggle the value of a bool value that is read
by the next constructor. As it is currently presented, one could reason
that the problem was simply in using a bool instead of an integer value.
Rather than toggle between false and true, one could think your objections
would be met by using incrementing (when memory is allocated by new) and
decrementing (by constructor), i.e. if the count is > 0, then assume a
"new" was used and then decrement. This much would take care of the
nesting problem -- or so it might seem to a reader.
However, I think the real problem and the deeper unresolved issue is that
the construction of nested objects could include an object that has a data
member of the class in question. That data member might not involve the
use of new, yet it may be constructed before the completion of the
construction of other instances that did use "new". So, the issue is not
merely the possibility of nesting construction (which could be dealt with
by the inc/dec method), but of the fact that that nesting could involve
both "new" and non-"new" instances of the class in question. Without the
more explicit comparison of addresses, one could not be sure that the use
of "new" was not being attributed to the wrong instances of the class.
Subject: Re: MEC++ Item 27 (require/prohib. heap) suggestions
Date: Wed, 21 Apr 1999 16:33:46 -0700
From: Scott Meyers
To: Eric Anderson
>Time -- Returning a bool value is much more efficient than doing a search
>through a sequential list of length N. This savings occurs with each use
>of isOnHeap.
True, but don't forget that because each object is now a bit larger (and,
due to alignment and padding issues, possibly by more than just the size of
a bool). That means that fewer objects will fit in cache or main memory.
This could compromise locality of reference if many objects are frequently
accessed together. Depending on the mix of operations performed, your
design might therefore run more slowly than the one in MEC++. It might
also run faster. A priori, there's just no way to know. This isn't a
criticism of your design, it's just an argument that your design is not
obviously faster.
>Plus, there is also a savings for each search that checks
>for removing and address from the list. If this operation is deferred to
>deletion time, that means the "new" list must contain entries for every
>undeleted object. This could be hundreds, thousands or more, depending on
>the application. Each such search must make an O(N) search of that
>potentially long list.
True, but there are other data structures with better access
characteristics, e.g., a hash table. My goal in the book was to come up
with a portable design that works, not to come up with the fastest possible
design.
>Space -- The space required for the extra bool member is much less than
>keeping a whole list entry in the static "new" list, which includes space
>for each address to remember, plus the overhead of the list pointers that
>tie the list together (e.g. two extra pointers, if it is doubly linked).
True, but as I note above, by making each object bigger, you may decrease
the speed of the application. Sometimes it's preferable to use more total
bytes in order to make each object as small as possible. Again, this is
not a criticism of your design, just a remark that size and speed sometimes
interact in unanticipated ways.
Subject: Re: MEC++ Item 27 (require/prohib. heap) suggestions
Date: Wed, 21 Apr 1999 22:17:00 -0400
From: Eric Anderson
To: Scott Meyers
One other minor observation is that Ron van der Wal's problem case of
new UPNumber(UPNumber());
is only the simplest specific case of the more general one I mention,
i.e. the nested creation of any object that contains a non-heap instance of
the class in question. In order to apply the appropriate constructor for
the enclosing new object, the compiler will have to finish creating the
nested object first (which becomes the argument to the enclosing
constructor), so the counter solution won't work.
From: "Bill Wade"
To: "Scott Meyers"
Subject: Re: New Web Page: Is an object on the heap?
Date: Thu, 22 Apr 1999 09:01:39 -0500
Scott Meyers, Ron van der Wal and Luke Tomasello discuss solutions that
involve having operator new() put a "signature" (or a bunch of signatures)
into a block so that the object's constructor can tell if the object is on
the heap. There is some discussion of "accidental" signatures. In the
book Scott presents a list-based method (with no signatures) that avoids
this problem, but can require O(N) time (N is number of heap objects) to
delete a single item.
There is an amortized O(1) technique to make signatures while avoiding the
false (accidental) signature problem. I first saw this as exercise 2.12 in
"The Design and Analysis of Computer Algorithms" by Aho, Hoppcroft and
Ullman. An interesting application was presented by P. Briggs and L.
Torczon, "An Efficient Representation for Sparse Sets", ACM Letters on
Programming Languages and Systems (LOPLAS) 2(1-4), March-December 1993,
pages 59-70.
Here is the technique to efficiently and unambiguously "sign" a word of
memory. It should be easy to extend to sign a range of words. To keep
things simple I'll ignore issues such as alignment or potential overlap of
words and build a class which keeps track of signed integers in memory.
Pointers must be dereferencable. You can't sign NULL.
I haven't even attempted to compile this code as written. All operations
are amortized constant time. There are two words of external storage
(class Notary::Entry) for each signed word. If vector is contiguous it is
easy to reduce the overhead to single word (in addition to the word being
signed). As written, at most MAX_INT words can be signed at any one time.
class Notary
{
public:
Notary();
// Sign a word. Word must not already be signed or overlap a signed word.
// Code does not check for overlap. Once a word is signed it should not be
// modified until after its signature has been Revoke'd.
void Sign(int *);
void Revoke(const int*); // Unsign a word. Word must already be signed.
bool IsSigned(const int*) const; // Tells if a word is signed.
private:
struct Entry
{
Entry(): word(0), next_free(-1){}
const int* word; // The word that is signed or NULL if not in use.
int next_free; // Index in logbook of next free Entry (linked list) or -1.
};
std::vector logbook;
int first_free; // Index of first (linked list) free Entry or -1.
};
Notary::Notary(): first_free(-1){}
void Notary::Sign(int* p)
{
assert(!IsSigned(p));
if(first_free == -1)
{ // logbook is full. Make some more room.
first_free = logbook.size();
logbook.resize(first_free+1);
}
// In the word being signed, remember the location of the entry in the logbook.
*p = first_free;
// Update the logbook entry to indicate the word is signed.
logbook[first_free].word = p;
// Remove the entry from the free list.
first_free = logbook[first_free].next_free;
}
void Notary::Revoke(const int* p)
{
assert(IsSigned(p));
int index = *p;
// Make the Entry point nowhere.
logbook[index].word = 0;
// Add the entry to the free list.
logbook[index].next_free = first_free;
first_free = index;
}
bool Notary::IsSigned(const int* p) const
{
// This next line assumes it is always ok to read an uninitialized integer
// as an integer. Strictly speaking I don't believe the standard allows that.
// You could make this code strictly conforming by only accessing *p as an array
// of characters (here and in Sign and Revoke), but that seems like overkill.
int index = *p;
// Is index in range?
if(index < 0 || logbook.size() <= index)
return false;
// Does the logbook agree that *p is the word associated with index?
return p == logbook[index].word;
}