将constness传播到成员变量指向的数据

时间:2011-01-18 22:32:12

标签: c++ pointers const smart-pointers

对于C ++新手来说,通常很容易让const成员函数在类引用的对象上调用非const方法(通过指针或引用)。例如,以下内容完全正确:

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_
};

然而,如果constness会传播到尖头对象,那么它有时会非常方便(我自愿使用PImpl习语,因为它是我认为“constness传播”非常有用的情况之一)。

使用指针时,可以通过使用某种智能指针轻松实现这一点,操作符在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_;
};

现在,我只需将SomeClass::impl_修改为const_propagating_ptr<SomeClassImpl>即可获得所需行为。

所以我对此有几个问题:

  1. 我忽略了constness传播的一些问题吗?
  2. 如果没有,是否有任何库提供类来获取常量传播?
  3. 常见的智能指针(unique_ptr,shared_ptr等)提供某种方法来获取此行为(例如通过模板参数)会不会有用?

4 个答案:

答案 0 :(得分:2)

  1. 正如@Alf P. Steinbach所说,你监督了一个事实,即复制你的指针会产生一个指向同一个底层对象的非const对象。 Pimpl(下面)通过执行深层复制来很好地规避问题,unique_ptr通过不可复制来绕过它。当然,如果指针对象由单个实体拥有,则要容易得多。

  2. Boost.Optional传播const-ness,但它并不是一个指针(尽管它模拟了OptionalPointee概念)。我知道没有其他这样的图书馆。

  3. 我赞成他们默认提供它。添加另一个模板参数(我猜的特征类)似乎不值得麻烦。然而,这会从经典指针中彻底改变语法,所以我不确定人们是否愿意接受它。


  4. Pimpl类的代码

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

答案 1 :(得分:2)

一种方法是直接使用指针,除非通过两个访问器函数。

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();
    }
};

答案 2 :(得分:1)

为了记录,我发现Loki library确实提供了一个const传播指针(ConstPropPtr<T>)。它看起来就像问题中的那个,除了它还删除了析构函数中的包装指针,它用于实现类似于Pimpl classthe one proposed by @Matthieu(但不可复制)。

答案 3 :(得分:0)

如果您认为它应该“传播”const-ness,那么这意味着您并不真的相信它是指针(或引用),但您认为它是容器:如果对象恒定时值是常量,那是因为对象包含值。

因此,复制对象会复制该值,至少在逻辑上(CoW)。

如果你坚持认为它是一个指针/参考IOW,你可以在共​​享包含的值时复制对象,那么你有一个不健全(矛盾)的界面

结论:下定决心。它是容器或指针。

指针不会传播常量,按照定义