c++ - constructor class
¿Por qué C++ no tiene un constructor de const? (5)
No sería un método const en sí mismo.
Si este constructor no fuera un método const
, los punteros internos y otros tampoco serían const
. Por lo tanto, no pudo establecer valores const
en esos miembros no const
.
La única forma de hacerlo funcionar de manera sintáctica es que este constructor requiera la inicialización de miembros para todos mutable
miembros no mutable
. Esencialmente, cualquier miembro no declarado mutable
se declararía implícitamente const
al usar este constructor. Lo que equivale a hacer del constructor un método const
; Sólo los inicializadores podrían inicializar miembros. El cuerpo del constructor no podría hacer nada con los miembros no mutables, porque esos miembros estarían const
en ese punto.
Lo que estás pidiendo es sintácticamente dudoso. Básicamente, estás tratando de engañar a la API, almacenando datos constantes en un objeto que está diseñado para datos mutables (por lo que no declaraste que el puntero del miembro es const
). Si desea un comportamiento diferente para un objeto, debe declarar que el objeto tiene ese comportamiento específico.
( Edición: Cambio pesado porque el ejemplo anterior fue defectuoso, lo que puede hacer que algunas respuestas / comentarios parezcan extraños)
Esto podría ser demasiado artificial, pero lo siguiente es legal debido a la falta de constructor de const:
class Cheater
{
public:
Cheater(int avalue)
: cheaterPtr(this) //conceptually odd legality in const Cheater ctor
, value(avalue)
{}
Cheater& getCheaterPtr() const {return *cheaterPtr;}
int value;
private:
Cheater * cheaterPtr;
};
int main()
{
const Cheater cheater(7); //Initialize the value to 7
cheater.value = 4; //good, illegal
cheater.getCheaterPtr().value = 4; //oops, legal
return 0;
}
Parece que proporcionarle a un constructor de const una cosa sería tan fácil técnicamente como los métodos de const, y sería análogo a una sobrecarga de const.
Nota: no estoy buscando '' Image( const Data & data ) const
'', sino '' const Image( const Data & data) const
. De const Image( const Data & data) const
Asi que:
- ¿Por qué está ausente el constructor const en C ++?
Aquí hay algunos materiales relacionados para el contexto:
En mi opinión, el hecho de que los controladores no tengan especificación de tipo de retorno es lo que falla aquí. Cualquier otra sintaxis imaginable como por ejemplo
class A
{
const A& ctor(...);
}
sería, imho, muy valioso. Por ejemplo, imagine tal situación de llamar a un método con prototipo.
void my_method(const my_own_string_class& z);
Si my_own_string_class tiene un ctor de char *, el compilador podría elegir este ctor, pero como este ctor no puede devolver un objeto const, debe asignar y copiar ... Si se permite el tipo const return, se podría hacer
class my_own_string_class
{
char *_ptr;
public:
const my_own_string_class& ctor(char *txt)
: _ptr(txt)
{ return *this;}
}
siempre que este constructo especial se limite a la creación de instancias temporales. (Y dtor debe ser mutable;)).
Los objetos const deberían inicializar sus variables miembro y un constructor const no podría hacerlo.
Mark B repasa las consideraciones fundamentales, pero tenga en cuenta que puede hacer algo similar en C ++ puro. Considerar:
struct Data { };
class ConstImage {
protected:
const Data *const_data;
public:
ConstImage (const Data *cd) : const_data(cd) { }
int getFoo() const { return const_data->getFoo(); }
};
class Image : public ConstImage {
protected:
Data *data() { return const_cast<Data *>(const_data); }
public:
Image(Data *d) : const_data(d) { }
void frob() { data()->frob(); }
};
En lugar de usar const Image *
, usa ConstImage *
, y listo. También podría simplemente definir una función estática pseudo-constructor:
const Image *Image::newConstImage(const Data *d) {
return new Image(const_cast<Data*>(d));
}
Esto, por supuesto, confía en el programador para asegurarse de que no haya funciones const
que puedan mutar de alguna manera el estado de Data
apuntado a.
También puedes combinar estas técnicas:
class Image {
protected:
const Data *const_data;
Data *data() { return const_cast<Data *>(const_data); }
public:
void frob() { data()->frob(); }
int getFoo() const { return const_data->getFoo(); }
Image(Data *d) : const_data(d) { }
static const Image *newConst(const Data *cd) {
return new Image(const_cast<Data *>(cd));
}
};
Esto consigue lo mejor de ambos mundos; dado que data()
es un miembro constante, tiene una comprobación estática de la mutación del valor apuntado. Sin embargo, también tiene un constructor de const y puede convertir directamente entre Image *
y const Image *
(es decir, puede eliminar la constness si sabe que es seguro).
También puede abstraer la separación de punteros más lejos:
template<typename T>
class ConstPropPointer {
private:
T *ptr;
public:
ConstPropPointer(T *ptr_) : ptr(ptr_) { }
T &operator*() { return *ptr; }
const T &operator*() const { return *ptr; }
T *operator->() { return ptr; }
const T *operator->() const { return ptr; }
};
class Image {
protected:
ConstPropPointer<Data> data;
public:
void frob() { data->frob(); }
int getFoo() const { return data->getFoo(); }
Image(Data *d) : data(d) { }
static const Image *newConst(const Data *cd) {
return new Image(const_cast<Data *>(cd));
}
};
Ahora, si this
es constante, los data
convierten en const, propagándolos también en *data
. Lo suficientemente bueno para usted? :)
Supongo que la respuesta final es probablemente esta: para que un constructor de const sea útil y seguro, necesitaríamos algo como el ConstPropPointer
que ves integrado en el lenguaje. A los constructores const se les permitiría entonces asignar desde const T *
a constprop T *
. Esto es más complejo de lo que parece, por ejemplo, ¿cómo interactúa esto con las clases de plantilla como vector
?
Entonces, este es un cambio un tanto complejo, pero el problema no parece surgir mucho. Más importante aún, hay una solución simple aquí (el ConstPropPointer
puede ser bibliotecario, y el pseudo-constructor estático es lo suficientemente simple como para agregarlo). Entonces, el comité de C ++ probablemente lo pasó por cosas más importantes, si es que se propuso.
Solo porque la Image
es const
en su constructor imaginario no significa que lo que m_data
apunta es. Terminarías pudiendo asignar un "puntero a const" a un "puntero const a no const" dentro de tu clase, lo que eliminaría la constancia sin una conversión. Obviamente, esto le permitiría violar invariantes y no se podría permitir.
Por lo que sé, cualquier conjunto específico de constancia que sea necesario puede especificarse de manera precisa y completa dentro del estándar actual.
Otra forma de verlo es que const
significa que el método no muta el estado de su objeto. El único propósito de un constructor es inicializar el estado de un objeto para que sea válido (de todas formas, cualquier constructor con efectos secundarios debe ser ... evaluado cuidadosamente).
EDITAR: En C ++, la constancia se aplica a ambos miembros, y para los punteros y referencias, a la constancia accesible del objeto referido. C ++ tomó conscientemente la decisión de dividir estas dos constricciones diferentes. En primer lugar, ¿estamos de acuerdo en que este código que demuestra la diferencia debe compilar e imprimir "no constantes"?
#include <iostream>
struct Data
{
void non_const() { std::cout << "non-const" << std::endl; }
};
struct Image
{
Image( Data & data ) : m_data( data ) {}
void check() const { m_data.non_const(); }
Data & m_data;
};
int main()
{
Data data;
const Image img(data);
img.check();
return 0;
}
Entonces para obtener el comportamiento donde podría aceptar un const-ref y almacenarlo como const-ref, la declaración efectiva de la referencia tendría que cambiar para ser const. Esto significaría entonces que sería un tipo completamente distinto, NO una versión const
del tipo original (ya que dos tipos con miembros que difieren en la calificación constante se tratan como dos tipos separados en C ++). Por lo tanto, o el compilador tiene que ser capaz de hacer un exceso de magia detrás de escena para convertir estas cosas de un lado a otro, recordando la constancia de los miembros, o debe tratarlo como un tipo separado que no podría ser utilizado en lugar del tipo normal.
Creo que lo que intentas lograr es un objeto referencee_const, un concepto que solo existe en C ++ como una clase separada (que sospecho que podría implementarse con el uso juicioso de las plantillas, aunque no lo intenté).
¿Es esta una pregunta estrictamente teórica (respuesta: C ++ decidió dividir el objeto y hacer referencia a la constancia) o hay un problema real no procesado que intentas resolver?