2011-01-18 9 views
22

A menudo es bastante confuso para los recién llegados en C++ que las funciones de miembros const puedan llamar a métodos no const en objetos referenciados por la clase (ya sea por puntero o referencia). Por ejemplo, la siguiente es perfectamente correcta:Propagar constness a los datos apuntados por las variables miembro

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, sería a veces ser bastante útil si el constness se propagaría a los objetos puntiagudos (utilicé voluntariamente el idioma PImpl porque es uno de los casos en el que creo " propagación de la consistencia "sería muy útil).

Cuando el uso de punteros, esto fácilmente se puede lograr mediante el uso de 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, sólo hay que modificar SomeClass::impl_ ser un const_propagating_ptr<SomeClassImpl> para obtener lo que se desea .

Así que tengo algunas preguntas acerca de este:

  1. ¿Hay algunos problemas con la propagación constness que he pasado por alto?
  2. Si no, ¿hay alguna biblioteca que proporcione clases para obtener la propagación de la consistencia?
  3. ¿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)?
+3

¿Qué sucede si solo copio el puntero inteligente? Voila, tengo uno no const. –

+1

'T const * const operator ->() const {return ptr_; } '- probablemente no sea necesario el segundo' const' aquí –

+0

@Alf y @robin: el boceto que di de una posible implementación probablemente esté plagado de errores (a pesar de su tamaño pequeño :)), no es el punto central del pregunta. Sin embargo, ¡tus comentarios son realmente apreciados! En cuanto al problema de la copia, no veo en este momento cómo podemos evitar que eso sea posible, pero a menudo no puedes evitar disparos en el pie (por ejemplo, siempre puedes "const_cast" constness de distancia, no significa que const sea inútil). En cuanto al segundo comentario, tienes razón @robin, lo hice equivocadamente para evitar que 'ptr_' sea ... –

Respuesta

2
  1. Como se señaló @Alf P. Steinbach, que supervisó el hecho de que la copia de su puntero produciría un objeto no constante apuntando al mismo objeto subyacente. Pimpl (abajo) bien evitar el problema mediante la realización de una copia profunda, unique_ptr lo evita al ser copiable. Es mucho más fácil, por supuesto, si el pointee es propiedad de una sola entidad.

  2. Boost.Optional propaga la const-ness, sin embargo, no es exactamente un puntero (aunque modela el concepto OptionalPointee). No conozco otra biblioteca así.

  3. Yo preferiría que lo proporcionen por defecto. Agregar el parámetro de otra plantilla (clase de rasgos, supongo) no parece valer la pena. Sin embargo, eso cambiaría radicalmente la sintaxis de un puntero clásico, por lo que no estoy seguro de que la gente esté dispuesta a abrazarlo.


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&> {}; 
2

Un enfoque consiste en no utilizar el puntero directamente sino 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(); 
    } 
}; 
+0

Esta es la solución que yo Intenté inicialmente, excepto que utilicé una instancia de clase anidada para encapsular el puntero, con el fin de evitar su acceso desde la clase externa (solo los getters tuvieron acceso gracias a las declaraciones de los amigos). Sin embargo, esta solución agrega mucho código repetitivo para cada puntero para el que necesitamos propagación de const. Además, en mi humilde opinión, el uso de getters y setters disminuye la legibilidad (las propiedades son una de mis características favoritas de C# :)!). –

1

Para el registro, me acabo de enterar que el Loki library sí provee una constante propagación de puntero (ConstPropPtr<T>). Se ve como el que está en la pregunta, excepto que también elimina el puntero envuelto en su destructor, y se usa para implementar un Pimpl class similar a the one proposed by @Matthieu (pero no se puede copiar).

0

Si usted piensa que debería "propagar" const-dad, entonces significa que realmente no cree que es un puntero (o de referencia), pero creen que es un recipiente: si el valor es constante cuando el objeto es constante, es porque el objeto contiene el valor.

Copiando así el objeto copia el valor, al menos lógicamente (CoW).

Si insiste en que es un puntero/referencia IOW que puede copiar el objeto mientras comparte el valor contenido, entonces tiene una interfaz no válida (contradictoria).

Conclusión: decídase. Es un contenedor o un puntero.

Un puntero no se propaga const-ness, por definición.

+2

Creo que no entendió el punto: incluso cuando un objeto es contenido (propiedad) por otro objeto, a veces necesita almacenarlo usando un puntero (polimorfismo, inicialización lenta, firewall de compilación, ...). Es en estos casos que la propagación de const sería útil: ya que * conceptualmente * el objeto está contenido por el objeto, no se debe permitir que los métodos const del objeto lo modifiquen. Sin embargo, acepto que mi clase de muestra 'SomeClass' debería haber definido un constructor de copia o una copia no permitida, para evitar compartir accidentalmente' impl_' entre instancias 'SomeClass' '. –

+0

_ "Creo que no entendió el punto: es posible que desee propagar const_" Lo entiendo, y omitió mi punto: quiere propagar la const-ness porque no quiere ** semáforo ** semántica. ¡No llames a esa clase 'something_pointer'! "_incluso cuando un objeto es contenido (propiedad) por otro objeto, a veces necesitas almacenarlo usando un puntero_" Sí, de hecho. – curiousguy

+0

Por cierto, en caso de que alguien discuta que los nombres no son tan importantes: si llamas algo "puntero" (que en realidad no tiene semántica de puntero), algún otro programador puede venir y agregar elementos relacionados con el puntero que no funcionan del todo tiene sentido para algo que no se supone que tiene puntero semántico. Al llamarlo contenedor, (en su mayoría) evita este riesgo. – curiousguy

Cuestiones relacionadas