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

发布于 2024-10-12 20:03:13 字数 1335 浏览 6 评论 0原文

对于 C++ 新手来说,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_
};

但是,如果常量传播到指向的对象,有时会相当方便(我自愿使用 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即可。 以获得所需的行为。

所以我对此有几个问题:

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

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:

  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)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

瞳孔里扚悲伤 2024-10-19 20:03:13
  1. 正如 @Alf P. Steinbach 所指出的,您看到了这样一个事实:复制指针会产生一个指向同一底层对象的非常量对象。 Pimpl(如下)通过执行深度复制很好地规避了该问题,unique_ptr 通过不可复制来规避该问题。当然,如果被指点者由单个实体拥有,那就容易得多。

  2. 传播常量性,但它并不完全是一个指针(尽管它模拟了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. 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&> {};
时常饿 2024-10-19 20:03:13

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

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

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();
    }
};
迎风吟唱 2024-10-19 20:03:13

作为记录,我刚刚发现 Loki 库 确实提供了一个 const 传播指针(ConstPropPtr)。它看起来就像问题中的那个,除了它还删除其析构函数中的包装指针,并且它用于实现 Pimpl 类似于 @Matthieu 提议的(但不可复制)。

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

唔猫 2024-10-19 20:03:13

如果您认为它应该“传播”常量,那么这意味着您并不真正相信它是一个指针(或引用),但您相信它是一个容器:如果当对象不变时值也不变,这是因为对象包含该值。

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

如果你坚持认为它是一个指针/引用IOW,你可以在共​​享所包含的值的同时复制对象,那么你就有一个不健全(矛盾)的接口。

结论:下定决心。它要么是一个容器,要么是一个指针。

根据定义,指针不会传播常量性。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文