对于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>
即可获得所需行为。
所以我对此有几个问题:
答案 0 :(得分:2)
正如@Alf P. Steinbach所说,你监督了一个事实,即复制你的指针会产生一个指向同一个底层对象的非const对象。 Pimpl
(下面)通过执行深层复制来很好地规避问题,unique_ptr
通过不可复制来绕过它。当然,如果指针对象由单个实体拥有,则要容易得多。
Boost.Optional传播const-ness,但它并不是一个指针(尽管它模拟了OptionalPointee概念)。我知道没有其他这样的图书馆。
我赞成他们默认提供它。添加另一个模板参数(我猜的特征类)似乎不值得麻烦。然而,这会从经典指针中彻底改变语法,所以我不确定人们是否愿意接受它。
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
class的the one proposed by @Matthieu(但不可复制)。
答案 3 :(得分:0)
如果您认为它应该“传播”const-ness,那么这意味着您并不真的相信它是指针(或引用),但您认为它是容器:如果对象恒定时值是常量,那是因为对象包含值。
因此,复制对象会复制该值,至少在逻辑上(CoW)。
如果你坚持认为它是一个指针/参考IOW,你可以在共享包含的值时复制对象,那么你有一个不健全(矛盾)的界面。
结论:下定决心。它是容器或指针。
指针不会传播常量,按照定义。