c++constantssmart-pointersboost-smart-ptr

Does boost::scoped_ptr violate the guideline of logical constness


In boost::scoped_ptr operator* and operator-> are declared const functions, though they return T& and T* which potentially allows clients to change the underlying data. This violates the idea of logical constness (Myers, Effective C++)

Shouldn't the const functions have had the signature ?

const T& operator*() const;
const T* operator->() const;

Solution

  • The fundamental issue here is that scoped_ptr objects behave more like pointers rather than class objects (even though scoped_ptr instances are in fact class objects).

    The smart pointer classes provided by Boost are designed to preserve raw pointer semantics as much as possible while providing additional functionality like reference counting or (in this case) RAII semantics.

    To that end, the operator*() and operator->() members of scoped_ptr is written so that it's "constness behavior" essentially matches that of a raw pointer.

    Consider this situation with "dumb" pointers:

    // Can change either Foo or ptr.
    Foo* ptr;
    // Can't change Foo via ptr, although ptr can be changed.
    const Foo* ptr;
    // Can't change ptr, although Foo can be changed via ptr.
    Foo* const ptr;
    // Can't change Foo or ptr.
    const Foo* const ptr;
    

    The scoped_ptr analogs would look like this:

    // Can change either Foo or ptr.
    scoped_ptr<Foo> ptr;
    // Can't change Foo via ptr, although ptr can be changed.
    scoped_ptr<const Foo> ptr;
    // Can't change ptr, although Foo can be changed via ptr.
    const scoped_ptr<Foo> ptr;
    // Can't change Foo or ptr.
    const scoped_ptr<const Foo> ptr;
    

    The way the operators were written makes the above code snippet possible, even though scoped_ptr isn't actually a raw pointer.

    In all cases, the code needs to be able to dereference ptr. By making the operators const, the dereference/member-access operators can be called on both const and non-const scoped_ptrs.

    Note that if a user declares a scoped_ptr<Foo>, it would have these members:

    Foo& operator*() const;
    Foo* operator->() const;
    

    while a scoped_ptr<const Foo> would have these members:

    const Foo& operator*() const;
    const Foo* operator->() const;
    

    So the const-correctness behavior of pointers is actually preserved this way.

    But no more, otherwise they wouldn't be smart pointers!