开发者

Propagate constness to data pointed by member variables

开发者 https://www.devze.com 2023-02-05 15:02 出处:网络
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 fol

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 ope开发者_运维知识库rator (?), 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:

  1. Are there some issues with constness propagation that I have overlooked?
  2. If not, are there any libraries that provide classes to obtain constness propagation?
  3. Wouldn't it be useful that the common smart pointers (unique_ptr, shared_ptr, etc.) provide some mean to obtain this behavior (for example through a template parameter)?


  1. As @Alf P. Steinbach noted, you oversaw 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.

  2. 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.

  3. 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&> {};


One approach is to just not use the pointer directly except through two accessor functions.

class SomeClass
{
  private:
    class SomeClassImpl;
    SomeClassImpl * impl_; // PImpl idiom - don't use me directly!

    SomeClassImpl * mutable_impl() { return impl_; }
    const SomeClassImpl * impl() const { return impl_; }

  public:    

    void const_method() const
    {
      //Can't use mutable_impl here.
      impl()->const_method();
    }
    void non_const_method() const
    {
      //Here I can use mutable_impl
      mutable_impl()->non_const_method();
    }
};


For the record, I just found out that the Loki library does provide a const propagating pointer (ConstPropPtr<T>). It looks just like the one in the question, except that it also deletes the wrapped pointer in its destructor, and it is used to implement a Pimpl class similar to the one proposed by @Matthieu (but not copyable).


If you think it should "propagate" const-ness, then it means you don't really believe it is a pointer (or reference), but you believe it is a container: if the value is constant when the object is constant, it's because the object contains the value.

So copying the object copies the value, at least logically (CoW).

If you insist that it is a pointer/reference IOW that you can copy the object while sharing the contained value, then you have an unsound (contradicting) interface.

Conclusion: make up your mind. It is either a container or a pointer.

A pointer does not propagate const-ness, by definition.

0

精彩评论

暂无评论...
验证码 换一张
取 消