$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Ed Brey (brey_at_[hidden])
Date: 2003-09-08 08:51:58
Douglas Gregor wrote:
> On Friday 05 September 2003 02:43 pm, Ed Brey wrote:
>> struct inert_bool_structure {};
>> typedef inert_bool_structure const* inert_bool;
>
> This allows several things we'd rather not allow:
> shared_ptr<T> p;
> p + 2; // not quite what we expected
> delete p; // dangerous
function_obj == p // bool comparison may be unexpected.
void const* v = p
These are all well-observed imperfections. (I tacked on the last two myself, to get the issues in one place.)
In the "p + 2" case, it would be better if the code didn't compile; OTOH, the results don't break from boolean semantics, so doesn't seem likely to confuse anyone.
The "delete p" case is fixed with a private destructor, i.e.:
struct inert_bool_structure { private: ~inert_bool_structure(); };
In the equality between different classes case, we enter mixed metaphor land. By one school of reasoning, if you can say:
if (function_obj) ...
and
if (shared_ptr_obj) ...
and
if (function_obj && shared_ptr_obj) ...
then wouldn't
if (function_obj == shared_ptr_obj) ...
be a logical extension? A counter argument is that this isn't the only way to think of the scenario, and a coding error can occur when someone is quite reasonable thinking in terms of content equality.
The void const* conversion case could hide a nasty bug, since the meaning subtly changes in shared_ptr by forgetting the .get(). No solution comes to mind for this.
The problems with inert_bool seem great enough to kill it. It is a shame in a way, since the efficiency problem of the pointer to member approach affects each usage, even though the added safety comes in handy only rarely.
Unless I missed something bool_testable only provides half the problem: it provides operator!, but not the implicit bool conversion. Perhaps the thinking is to add a templated bool conversion. This would solve the heterogeneous class comparison problem, but not the void const* problem. Still, maybe it is the best compromise.
An entirely different approach is to use a null class.
struct null_t {
template<typename T> operator T*() const {return 0;}
};
null_t const null;
template<typename T> inline bool operator==(shared_ptr<T> const& p, null_t) {return p.get() == 0;}
template<typename T> inline bool operator==(null_t, shared_ptr<T> const& p) {return p.get() == 0;}
template<typename T> inline bool operator!=(shared_ptr<T> const& p, null_t) {return p.get() != 0;}
template<typename T> inline bool operator!=(null_t, shared_ptr<T> const& p) {return p.get() != 0;}
In this approach, shared_ptr wouldn't include any boolean operator at all. Instead, code using would look like this:
if (p != null) ...
Not as nice an terse as just "if (p)", but safer and more explicit, and still prettier, IMHO, than "if (p.get())". A side benefit to this approach is that a null object would be a nice utility addition to boost anyway, since it is less ambiguous than 0 (although it doesn't work on pointer to members ... ah those pesky tradeoffs).
Further thoughts?
Ed