Распространение константности данным, указанным переменными-членами - PullRequest
22 голосов
/ 19 января 2011

Для новичков в C ++ часто довольно странно, что константные функции-члены могут вызывать неконстантные методы для объектов, на которые ссылается класс (либо по указателю, либо по ссылке). Например, следующее совершенно правильно:

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

Однако иногда было бы весьма удобно, если бы константность распространялась на заостренные объекты (я добровольно использовал идиому PImpl, потому что это один из случаев, когда я думаю, что "распространение константности" было бы очень полезно).

При использовании указателей это может быть легко достигнуто с помощью какого-то интеллектуального указателя с операторами, перегруженными по константности:

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. Есть ли какие-то проблемы с распространением констант, которые я пропустил?
  2. Если нет, существуют ли библиотеки, предоставляющие классы для получения распространения константности?
  3. Разве не было бы полезно, чтобы обычные умные указатели (unique_ptr, shared_ptr и т. Д.) Предоставляли какое-то средство для получения такого поведения (например, через параметр шаблона)?

Ответы [ 4 ]

2 голосов
/ 19 января 2011

Один из подходов состоит в том, чтобы просто не использовать указатель напрямую, кроме как через две функции доступа.

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 голосов
/ 19 января 2011
  1. Как заметил @Alf P. Steinbach, вы заметили тот факт, что копирование указателя приведет к неконстантному объекту, указывающему на тот же базовый объект. Pimpl (ниже) прекрасно обходит проблему, выполняя глубокое копирование, unique_ptr обходит ее, не копируя. Конечно, гораздо проще, если владелец путинского объекта принадлежит одному лицу.

  2. Boost.Optional распространяет константу, однако это не совсем указатель (хотя он моделирует концепцию OptionalPointee). Я не знаю такой другой библиотеки.

  3. Я бы предпочел, чтобы они предоставили его по умолчанию. Добавление другого параметра шаблона (я полагаю, класс черт) не стоит проблем. Однако это радикально изменило бы синтаксис от классического указателя, поэтому я не уверен, что люди будут готовы принять его.


Код 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 голос
/ 27 сентября 2011

Для записи, я только что обнаружил, что библиотека Loki обеспечивает постоянный распространяющийся указатель (ConstPropPtr<T>).Он выглядит так же, как в вопросе, за исключением того, что он также удаляет обернутый указатель в своем деструкторе и используется для реализации Pimpl класса , аналогичного , предложенного @Matthieu (но не для копирования).

0 голосов
/ 23 ноября 2011

Если вы думаете, что он должен «распространять» константность, то это означает, что вы на самом деле не верите, что это указатель (или ссылка), но вы верите, что это контейнер : если значение постоянное, когда объект постоянный, то это потому, что объект содержит значение.

Таким образом, копирование объекта копирует значение, по крайней мере, логически (CoW).

Если вы настаиваете на том, что это указатель / ссылка на IOW, что вы можете скопировать объект, разделяя содержащееся в нем значение, то у вас есть ненадежный (противоречащий) интерфейс .

Заключение : примите решение. Это либо контейнер, либо указатель.

Указатель не распространяет константу, по определению .

...