Подтвердить что ты не робот

Пропагандировать константу к данным, указанным переменными-членами

Часто бывает сложно сбивать новичков С++, чтобы функции-члены-члены разрешали вызывать неконстантные методы для объектов, на которые ссылается класс (либо указателем, либо ссылкой). Например, совершенно правильно:

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, потому что это один из тех случаев, когда я думаю, что "распространение констант" было бы очень полезно).

При использовании указателей это может быть легко достигнуто с помощью какого-то умного указателя с перегруженными операторами на 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>, чтобы получить желаемое поведение.

Итак, у меня есть несколько вопросов по этому поводу:

  • Есть ли некоторые проблемы с распространением constost, которые я упустил?
  • Если нет, существуют ли библиотеки, которые предоставляют классы для получения распространения констант?
  • Не было бы полезно, чтобы обычные интеллектуальные указатели (unique_ptr, shared_ptr и т.д.) обеспечивали некоторое значение для получения этого поведения (например, через параметр шаблона)?
4b9b3361

Ответ 1

  • Как заметил @Alf P. Steinbach, вы наблюдали за тем, что копирование указателя приведет к тому, что объект, не являющийся объектом const, укажет на один и тот же базовый объект. Pimpl (ниже) хорошо обойти проблему, выполняя глубокую копию, unique_ptr обходит ее, не будучи скопированной. Разумеется, намного проще, если плацдарм принадлежит одному объекту.

  • Boost.Optional распространяется на const-ness, однако это не точно указатель (хотя он моделирует концепцию OptionPointee). Я не знаю такой другой библиотеки.

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


Код класса 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&> {};

Ответ 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();
    }
};

Ответ 3

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

Ответ 4

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

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

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

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

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