resueltos - metodos en c++
Propagar constness a datos apuntados por variables miembro (4)
A menudo es bastante confuso para los recién llegados de C ++ que las funciones de miembros const puedan llamar a métodos no constantes en objetos referenciados por la clase (ya sea por puntero o por referencia). Por ejemplo, lo siguiente es perfectamente correcto:
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_
};
Sin embargo, a veces sería bastante útil si la constancia se propagara a objetos puntiagudos (utilicé voluntariamente el lenguaje PImpl porque es uno de los casos en los que creo que la "propagación de la constancia" sería muy útil).
Cuando se usan punteros, esto se puede lograr fácilmente usando algún tipo de puntero inteligente con operadores sobrecargados en 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_;
};
Ahora, solo necesito modificar SomeClass::impl_
para ser una const_propagating_ptr<SomeClassImpl>
para obtener el comportamiento deseado.
Así que tengo algunas preguntas sobre esto:
- ¿Hay algunos problemas con la propagación de constness que he pasado por alto?
- Si no es así, ¿hay bibliotecas que proporcionen clases para obtener propagación de constness?
- ¿No sería útil que los punteros inteligentes comunes (unique_ptr, shared_ptr, etc.) proporcionen algún medio para obtener este comportamiento (por ejemplo, a través de un parámetro de plantilla)?
Como @Alf P. Steinbach señaló, usted supervisó el hecho de que al copiar su puntero se obtendría un objeto no constante que apunta al mismo objeto subyacente.
Pimpl
(a continuación) evita el problema al realizar una copia profunda,unique_ptr
evita al no poder copiarse. Por supuesto, es mucho más fácil si el titular es propiedad de una sola entidad.Boost.Optional propaga la Boost.Optional , sin embargo, no es exactamente un puntero (aunque modela el concepto OptionalPointee). No conozco ninguna otra biblioteca.
Yo favorecería que lo proporcionen por defecto. Agregar otro parámetro de plantilla (supongo que la clase de rasgos) no parece que valga la pena. Sin embargo, eso cambiaría radicalmente la sintaxis de un puntero clásico, así que no estoy seguro de que la gente esté lista para abrazarla.
Código de la clase 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&> {};
Para el registro, acabo de descubrir que la biblioteca Loki proporciona un puntero de propagación constante ( ConstPropPtr<T>
). Se parece al de la pregunta, excepto que también elimina el puntero envuelto en su destructor, y se utiliza para implementar una clase Pimpl
similar a la propuesta por @Matthieu (pero no se puede copiar).
Si cree que debería "propagarse" la constancia, entonces significa que realmente no cree que sea un puntero (o referencia), pero cree que es un contenedor : si el valor es constante cuando el objeto es constante, es porque el objeto contiene el valor.
Entonces, al copiar el objeto se copia el valor, al menos de manera lógica (CoW).
Si insiste en que es un puntero / referencia IOW que puede copiar el objeto mientras comparte el valor contenido, entonces tiene una interfaz errónea (contradictoria) .
Conclusión : decídete. Es un contenedor o un puntero.
Un puntero no se propaga constancia, por definición .
Un enfoque es simplemente no usar el puntero directamente, excepto a través de dos funciones de acceso.
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();
}
};