nisah
nisah

Reputation: 2530

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;

Upvotes: 3

Views: 530

Answers (3)

curiousguy
curiousguy

Reputation: 8268

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.

The "underlying data" is not part of the smart pointer value. Two (smart) pointers are equal iff they point to the same object: a == b iff &*a == &*b.

This violates the idea of logical constness (Myers, Effective C++)

No, it does not:

The logical value of a smart pointer depends only on what it points to.

Dereferencing a smart pointer does not change what it points to.

So dereferencing a smart pointer does not change its logical value (or its state if you prefer).

QED

Upvotes: 1

In silico
In silico

Reputation: 52159

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!

Upvotes: 6

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385194

A scoped_ptr<T> is like a T*. It is not like a T* const.

A scoped_ptr<T const> is like a T const* (which you might write as const T*) and only then would you expect operator* and operator-> to return const things.

Upvotes: 0

Related Questions