|
Boost : |
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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk