C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 16:
16.1 Does delete p delete the pointer p, or the pointed-to-data *p?
16.2 Is it safe to delete the same pointer twice?
16.3 Can I free() pointers allocated with new? Can I delete pointers allocated with malloc()?
16.4 Benefits of new over malloc()?
16.5 Can I use realloc() on pointers allocated via new?
16.6 Checking for NULL after p = new Fred()?
16.7 How can I convince my (older) compiler to automatically check new to see if it returns NULL?
16.8 Checking for NULL before delete p?
16.9 What are the two steps that happen when I say delete p?
16.10 Does p = new Fred() leak memory if the ctor throws an exception?
16.11 How do I allocate / unallocate an array of things?
16.12 What if I forget the [] when deleteing an array allocated via new T[n]?
16.13 Can I drop the [] when deleteing an array of some built-in type (char, int, etc)?
16.14 After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?
16.15 Is it legal (and moral) for a member function to say delete this?
16.16 How do I allocate multidimensional arrays using new?
16.17 How to simplify the Matrix code from the previous FAQ?
16.18 How to make the Matrix class generic?
16.19 What's another way to build a Matrix template?
16.20 Does C++ have arrays whose length can be specified at run-time?
16.21 Allocating all objects via new, not local/global/static?
16.22 How do I do simple reference counting?
16.23 How do I provide reference counting with copy-on-write semantics?
16.24 How do I provide reference counting with copy-on-write semantics for a hierarchy of classes?
16.25 Preventing people from subverting the reference counting mechanism?
16.26 Can I use a garbage collector in C++?
16.27 What are the two kinds of garbage collectors for C++?
16.28 Where can I get more info on garbage collectors for C++?
[16.25] Can I absolutely prevent people from subverting the reference counting mechanism, and if so, should I?

No, and (normally) no.

There are two basic approaches to subverting the reference counting mechanism:

  1. The scheme could be subverted if someone got a Fred* (rather than being forced to use a FredPtr). Someone could get a Fred* if class FredPtr has an operator*() that returns a Fred&: FredPtr p = Fred::create(); Fred* p2 = &*p;. Yes it's bizarre and unexpected, but it could happen. This hole could be closed in two ways: overload Fred::operator&() so it returns a FredPtr, or change the return type of FredPtr::operator*() so it returns a FredRef (FredRef would be a class that simulates a reference; it would need to have all the methods that Fred has, and it would need to forward all those method calls to the underlying Fred object; there might be a performance penalty for this second choice depending on how good the compiler is at inlining methods). Another way to fix this is to eliminate FredPtr::operator*() — and lose the corresponding ability to get and use a Fred&. But even if you did all this, someone could still generate a Fred* by explicitly calling operator->(): FredPtr p = Fred::create(); Fred* p2 = p.operator->();.
  2. The scheme could be subverted if someone had a leak and/or dangling pointer to a FredPtr. Basically what we're saying here is that Fred is now safe, but we somehow want to prevent people from doing stupid things with FredPtr objects. (And if we could solve that via FredPtrPtr objects, we'd have the same problem again with them). One hole here is if someone created a FredPtr using new, then allowed the FredPtr to leak (worst case this is a leak, which is bad but is usually a little better than a dangling pointer). This hole could be plugged by declaring FredPtr::operator new() as private, thus preventing someone from saying new FredPtr(). Another hole here is if someone creates a local FredPtr object, then takes the address of that FredPtr and passed around the FredPtr*. If that FredPtr* lived longer than the FredPtr, you could have a dangling pointer — shudder. This hole could be plugged by preventing people from taking the address of a FredPtr (by overloading FredPtr::operator&() as private), with the corresponding loss of functionality. But even if you did all that, they could still create a FredPtr& which is almost as dangerous as a FredPtr*, simply by doing this: FredPtr p; ... FredPtr& q = p; (or by passing the FredPtr& to someone else).

And even if we closed all those holes, C++ has those wonderful pieces of syntax called pointer casts. Using a pointer cast or two, a sufficiently motivated programmer can normally create a hole that's big enough to drive a proverbial truck through. (By the way, pointer casts are evil.)

So the lessons here seem to be: (a) you can't prevent espionage no matter how hard you try, and (b) you can easily prevent mistakes.

So I recommend settling for the "low hanging fruit": use the easy-to-build and easy-to-use mechanisms that prevent mistakes, and don't bother trying to prevent espionage. You won't succeed, and even if you do, it'll (probably) cost you more than it's worth.

So if we can't use the C++ language itself to prevent espionage, are there other ways to do it? Yes. I personally use old fashioned code reviews for that. And since the espionage techniques usually involve some bizarre syntax and/or use of pointer-casts and unions, you can use a tool to point out most of the "hot spots."