It is often quite confusing to C++ newcomers that const member functions are allowed to call non-const methods on objects referenced by the class (either by pointer or reference). For example, the following is perfectly correct:
class SomeClass
{
class SomeClassImpl;
SomeClassImpl * impl_; // PImpl idiom
public:
void const_method() const;
};
struct SomeClass::SomeClassImpl
{
void non_const_method() { /*modify data*/ }
};
void SomeClass::const_method() const
{
impl_->non_const_method(); //ok because impl_ is const, not *impl_
};
However, it would sometimes be rather handy if the constness would propagate to pointed objects (I voluntarily used the PImpl idiom because it is one of the case where I think "constness propagation" would be very useful).
When using pointers, this can easily be achieved by using some kind of smart pointer with operators overloaded on constness:
template < typename T >
class const_propagating_ptr
{
public:
const_propagating_ptr( T * ptr ) : ptr_( ptr ) {}
T & operator*() { return *ptr_; }
T const & operator*() const { return *ptr_; }
T * operator->() { return ptr_; }
T const * operator->() const { return ptr_; }
// assignment operator (?), get() method (?), reset() method (?)
// ...
private:
T * ptr_;
};
Now, I just need to modify SomeClass::impl_
to be a const_propagating_ptr<SomeClassImpl>
to obtain the wanted behavior.
So I have a few questions about this:
As @Alf P. Steinbach noted, you overlooked the fact that copying your pointer would yield a non-const object pointing to the same underlying object. Pimpl
(below) nicely circumvent the issue by performing a deep-copy, unique_ptr
circumvents it by being non-copyable. It is much easier, of course, if the pointee is owned by a single entity.
Boost.Optional propagates const-ness, however it's not exactly a pointer (though it models the OptionalPointee concept). I know of no such other library.
I would favor that they provide it by default. Adding another template parameter (traits class I guess) does not seem worth the trouble. However that would radically change the syntax from a classic pointer, so I am not sure that people would be ready to embrace it.
Code of the Pimpl
class
template <class T>
class Pimpl
{
public:
/**
* Types
*/
typedef T value;
typedef const T const_value;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
/**
* Gang of Four
*/
Pimpl() : _value(new T()) {}
explicit Pimpl(const_reference v) : _value(new T(v)) {}
Pimpl(const Pimpl& rhs) : _value(new T(*(rhs._value))) {}
Pimpl& operator=(const Pimpl& rhs)
{
Pimpl tmp(rhs);
swap(tmp);
return *this;
} // operator=
~Pimpl() { boost::checked_delete(_value); }
void swap(Pimpl& rhs)
{
pointer temp(rhs._value);
rhs._value = _value;
_value = temp;
} // swap
/**
* Data access
*/
pointer get() { return _value; }
const_pointer get() const { return _value; }
reference operator*() { return *_value; }
const_reference operator*() const { return *_value; }
pointer operator->() { return _value; }
const_pointer operator->() const { return _value; }
private:
pointer _value;
}; // class Pimpl<T>
// Swap
template <class T>
void swap(Pimpl<T>& lhs, Pimpl<T>& rhs) { lhs.swap(rhs); }
// Not to be used with pointers or references
template <class T> class Pimpl<T*> {};
template <class T> class Pimpl<T&> {};