3

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;
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
nisah
  • 2,478
  • 3
  • 22
  • 20

3 Answers3

6

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!

In silico
  • 51,091
  • 10
  • 150
  • 143
  • "_pointers rather than objects_" actually pointers **are** objects – curiousguy Nov 22 '11 at 03:02
  • @curiousguy: That may be true (depending on what definition of "object" you use), but I make the distinction for the purposes of explaining why the smart pointers are designed the way they are with respect to const correctness. That being said, I've changed it to a "class object" to avoid any more misunderstandings. – In silico Nov 24 '11 at 03:41
  • My definition is the C++ definition: an object is a region of storage. An `int` variable is an object. – curiousguy Nov 24 '11 at 04:19
  • "_behave more like pointers rather than class objects_" to behave "like a class object" is a subjective notion. Some classes are meant to have value semantic, others have reference semantic. Iterators have reference semantic WRT to the data they refer to. It's a matter of intended class semantic. – curiousguy Nov 24 '11 at 04:34
  • @curiousguy: So? Smart pointers are still class objects, as opposed to native pointers, no? The issue at hand in the OP's question is why are the smart pointers operators defined in this way wrt constness as opposed to say, a `vector`'s array subscript operator. It's because of the fact smart pointers classes are supposed to act like pointers, unlike other classes like `vector`s where they are values in their own right. – In silico Nov 24 '11 at 18:03
  • "_Smart pointers are still class objects, as opposed to native pointers_" User defined types are supposed to behave like builtins in C++, so there is little "opposition" between a pointer and a class object. "_It's because of the fact smart pointers classes are supposed to act like pointers, unlike other classes like vectors where they are values in their own right._" A pointer is also a value "in its own right". You can copy pointer values, compare them, etc. – curiousguy Nov 25 '11 at 01:10
  • `std::copy` works for `std::vector` or for `int*`, and in both cases copies the **value** of the element. But the value of a `std::vector` consists of all the values of its elements, and the value of a pointer consists of which object it points to, not the value of the pointed-to object. – curiousguy Nov 25 '11 at 01:13
  • @curiousguy: Yes, but unlike your typical "everyday object" which can be either be `const` or non-`const`, a pointer can be qualified four different ways with respect to `const`-ness: The pointer value itself can be `const` or non-`const`, and the value that the pointer refers to can be considered to be `const` or non-`const`. That's the entire point of the way the smart pointer operators are defined wrt constness. There's a big difference between pointers and things that are not pointers, at least with `const`-ness. Smart pointers emulate this difference. – In silico Nov 25 '11 at 02:44
  • Whenever a type is defined based on some other type, there are 4 combinations, as in `struct { int i; } Ti, const cTi; struct { const int i; } Tci, const cTci;` or `array ai, const cai; array aci, const caci;`... – curiousguy Nov 25 '11 at 03:14
  • @curiousguy: Perhaps, but in the pointer case one of the four combinations can be specified solely at the pointer variable declaration, where in your `struct` example you need to specify constness in both the class definition and the variable declaration to get all four combinations. And in the array case, assuming the class is designed correctly, [a `const array caci` is redundant](http://stackoverflow.com/questions/2868485) since a `const array cai` will also have non-modifiable elements. The same cannot be said for raw/smart pointers. – In silico Nov 25 '11 at 03:40
1

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

curiousguy
  • 8,038
  • 2
  • 40
  • 58
0

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055