2010-08-25 8 views
6

¿Tendría sentido tener una operación "constify" en C/C++ que haga una variable const?¿Tendría sentido tener una operación 'constify' en C++?

He aquí un ejemplo en el que podría ser útil, donde obviamente no queremos declarar que const sin embargo, en la primera línea:

std::vector<int> v; 
v.push_back(5); 
constify v; // now it's const 

En la actualidad, sin esa posibilidad, habría que introducir otra variable para obtener el mismo efecto: (¿o utilizar swap)

std::vector<int> v0; 
v0.push_back(5); 
const std::vector<int>& v = v0; 

Eso es más confuso, ya que añade un nuevo nombre en el campo y lo que necesita para que sea una referencia para evitar la copia de todo el vector.

+3

¿Qué sucede si constifica v en una sola rama de un if-then-else? –

+1

Entonces probablemente debería ser 'const' solo en ese ámbito. – Frank

+3

No. No tendría sentido. –

Respuesta

19

Francamente, creo que es menos confuso si una variable es o bien const o no, que si esto puede cambiar.


Para elaborar un poco sobre esto: La razón por la que normalmente se quiere hacer esto es porque no se puede inicializar una variable const la forma en que desea. std::vector es un buen ejemplo de esto. Bueno, por una vez, la siguiente norma introduce una sintaxis de inicialización universal que hace esto posible:

const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 

Sin embargo, incluso sin cosas C++ 1x' a la mano, e incluso con tipos que no permiten esta sintaxis de inicialización, puede siempre crear una función auxiliar para hacer lo que quiera:

const std::vector<int>& cvi = create_my_vector(); 

o, si se quiere ser de lujo:

const std::vector<int>& cvi = compile_time_list<1,2,3,4,5,42>::create_vector(); 

Nota del &. No tiene sentido copiar el resultado de la llamada a la función, ya que vincular un valor r a una referencia const amplía su duración hasta el final de la vida útil de la referencia.
Por supuesto, volver a compilar con un compilador que admita la semántica de movimiento de C++ 1x hará que esas optimizaciones sean prácticamente innecesarias. Pero vincular un rvlaue a una referencia const puede ser aún más rápido que mover un vector y es poco probable que sea más lento.
Con C++ 1x, también puede crear funciones lambda haciendo esto una sola vez. C++ solo proporciona un arsenal de herramientas increíblemente grande. IME, no importa cuánto haya pensado, alguien más debería pensar en otra idea para hacer lo mismo. Y a menudo uno mejor que el tuyo.


Sin embargo, IME este problema generalmente solo viene con demasiado código en muy pocas funciones de todos modos. Y luego no solo se aplica a la constness, sino también a rasgos similares, como a lo que se refiere una referencia.
Un clásico es el uso-uno-de-varios-posibles-streams.En lugar de esto

int main(int argc, char* argv[]) 
{ 
    std::istream* istrm = NULL; 
    std::ifstream ifs; 
    if(argc > 1) 
    { 
    ifs.open(argv[1]); 
    if(ifs.good()) 
     istrm = &ifs; 
    } 
    if(!istrm) 
    istrm = &std::cin; 

    while(istrm->good()) 
    { 
    // reading from *istrm implemented here 
    } 
    return 0; 
} 

simplemente dividir las preocupaciones en: 1) averiguar dónde leer y 2) la lectura real:

int read(std::istream& is) 
{ 
    while(is.good()) 
    { 
    // reading from is implemented here 
    } 
    return 0; 
} 

int main(int argc, char* argv[]) 
{ 
    if(argc > 1) 
    { 
    std::ifstream ifs(argv[1]); 
    if(ifs.good()) 
     return read(ifs); 
    } 
    return read(std::cin); 
} 

todavía tengo que ver un ejemplo del mundo real de una variable eso no fue tan constante como podría haber sido, lo que no pudo solucionarse separando las preocupaciones.

+0

+1: Tengo que aceptar que el hecho de que 'const' no se aplique durante la ejecución del ctor o dtor ya causa confusión suficiente. –

+0

Creo que te refieres a C++ 0x, no a C++ 1x. http://www2.research.att.com/~bs/C++0xFAQ.html – Ben

+0

@Ben: El marco de tiempo para C++ 0x termina este año y no habrá un nuevo estándar este año. Es probable que salga el próximo año, lo que lo convertiría en C++ 11. Si hay más retrasos, será C++ 12. De ahí el C++ 1x. – sbi

6

Este es un buen momento para utilizar una función

#include <vector> 

std::vector<int> makeVector() 
{ 
    std::vector<int> returnValue; 
    returnValue.push_back(5); 
    return returnValue; 
} 

int main() 
{ 
    const std::vector<int> myVector = makeVector(); 
} 
+2

Su solución tiene una semántica diferente. El OP desea crear 1 'vector ' y cambiar la const non-const del identificador. Su solución crea 2 copias del 'vector ' con 2 diferentes palancas de acceso – JaredPar

+6

@Jared: Lo leí como: El OP quiere crear esencialmente un vector constante, pero no puede debido a la falta de listas de inicializadores de C++ 0x , o no tener todos los datos a la vez. – Bill

+8

@Jared: es probable que esta versión cree solo un vector (debido a la optimización del valor de retorno), que puede modificar en un ámbito y luego se vuelve constante en otro, igual que OP deseado. – UncleBens

8

Básicamente, se está tratando de reproducir el efecto de un constructor - es decir, const sólo se aplica después del constructor completa (y sólo hasta que el dtor es invocado). Como tal, lo que necesita es otra clase que envuelva su vector y lo inicialice en el ctor. Una vez que el ctor completa y vuelve, la instancia se convierte en const (suponiendo, por supuesto, que se definió como const).

C++ 0x mejorará considerablemente el requisito de envoltura como esta. Podrás usar los inicializadores de corchetes para vectores para crear/inicializar el vector en una sola operación. Otros tipos apoyarán (al menos potencialmente) a los inicializadores definidos por el usuario para lograr más o menos lo mismo.

7

C++ está tipado estáticamente. Para mí, la introducción de tal operación sería una violación de este paradigma y causaría mucha confusión.

+2

Esto sigue siendo tipado estático: el compilador puede inferir cuándo la variable es const o no en tiempo de compilación. – liori

+3

@liori: Tal vez. Si el tipo de una variable puede cambiar durante la ejecución, esto significa que tenemos que hacer un análisis de ruta de ejecución para hacer tipeo estático. Eso no funcionará en todos los casos y causará resultados sorprendentes en otros. –

+0

Ah, sí, se me olvidó eso. +1. – liori

3

Pensé en esto también. Pero, en mi humilde opinión, creará mucha confusión, que superará sus beneficios. Ahora que lo pienso, toda la noción de constness en C++ ya es bastante confusa.

Su idea se reduce a "¿Cómo puedo hacer que una variable sea de solo lectura una vez que se ha inicializado?". Puede obtener el mismo efecto haciendo que su variable sea un miembro privado de una clase, que se inicializa en el constructor y para la que proporciona un getter pero no establece.

3

Ya se ha mencionado que C++ 0x resuelve este un poco con abrazadera-inicializadores:

const std::vector<int> values{1, 2, 3, 4, 5}; 

Aunque esto sólo permite la inicialización, y no permite, por ejemplo, invocando const funciones no miembros después el constructor se ha ejecutado. Se es posible definir una macro constify de la siguiente manera:

#define constify(type, id) \ 
for (type const& id##_const(id), & id(id##_const), \ 
    * constify_index = &id; constify_index; constify_index = 0) 

que se pueden utilizar de este modo:

std::vector<int> v; 

// v is non-const here. 

constify (std::vector<int>, v) { 

    // v is const here. 

} 

Esto funciona mediante la creación de un bucle for que ejecuta la siguiente sentencia o bloque solamente una vez, con la variable constificada local al cuerpo del bucle. Tenga en cuenta la declaración de la variable de ayuda i_const antes de que el local de i: la declaración int const& i(i) inicializa i a — es decir, a un valor no inicializado — y queremos que el (i) que se refieren más bien a la previamente declarada i, por lo que un nivel extra es necesario.

Si se puede hacer uso de C++ 0x características, la palabra clave decltype viene muy bien, lo que le permite omitir el tipo de invocaciones de constify:

#define constify(id) \ 
for (decltype(id) const& id##_const(id), & id(id##_const), \ 
    * constify_index = &id; constify_index; constify_index = 0) 

¿Qué le permite escribir, simplemente:

constify (v) { 
    // ... 
} 

Ambas versiones funcionan si la variable se declara inicialmente const o no. Entonces, sí, algo muy parecido a lo que estabas buscando es posible, pero probablemente no valga la pena.

+2

El uso de 'const_cast' de esta manera da como resultado un comportamiento indefinido. –

+0

Esto no es seguro, ya que cada vez que declara una variable como 'const', el compilador puede optar por poner todo o parte de ella en memoria de solo lectura, dando lugar a fallas si rechaza const e intenta modificar el objeto. Esto es poco probable para una clase compleja como 'std :: vector' pero sigue siendo una preocupación. –

+0

Oh, bueno. Dije que no era seguro. –

3

Supongo que estás hablando de algo más genérico que solo los vectores de inicialización (que se resuelve en C++ 0x), y usa vectores solo como ejemplo.

prefiero verlo hecho a través de algún tipo de funciones locales:

const vector<int> values = []{ 
    vector<int> v; 
    copy(some_other_data.begin(), some_other_data.end(), v); 
    sort(v); 
    return v; 
}(); 

(que podría estropear sintaxis de la función anónima de C++ 0x). Esto lo puedo leer con toda naturalidad como: "prepare un vector const según la rutina que se describe aquí". Solo la cantidad de paréntesis me molesta un poco.

Puedo ver cómo este código podría convertirse en un modismo de C++ después de que C++ 0x se vuelve más natural para los programadores.

(editado después de la sugerencia de dehmann)

+1

Buena respuesta. Y sí, parece que no necesitas todos los paréntesis en '[]() {...}'. Los paréntesis redondos son opcionales, por lo que se convierte en '[] {...}' – Frank

0

Usted puede envolver el vector en una clase, declaran el vector envuelta mutable y, a continuación, crea una instancia const de la envoltura. La clase de envoltura puede cambiar el vector pero llamantes externos ver un objeto constante

2

Actualmente, const o no es algo que el compilador sabe, por lo que el compilador no aceptará un programa que trata de cambiar una variable const.

Si quería hacer un operador de constify, que tendría que hacer de esto una característica de la variable (sin más palabras clave, de cada variable) por lo que puede cambiar en tiempo de ejecución. Y, por supuesto, tendría que lanzar una excepción cada vez que un programa intente cambiar una variable (actualmente) const, lo que significa que cada acceso de escritura a cada variable debe verificar primero la propiedad const.

Todo esto va en contra de la filosofía de C++ y de todos los demás lenguajes estáticos. Y también rompe la compatibilidad binaria con libs existentes.

2

Consideremos el siguiente bit:

void foo(std::vector<int> & v) 
{ 
    v.push_back(1); 
    constify v; 
} 
void bar() { 
    std::vector<int> test(7); 
    foo(test); 
    test.clear(); 
} 

es la variable foo v en constified? Es la misma variable que test en la barra. Por lo tanto, la llamada test.clear() no debería ser válida. Creo que lo que realmente quiso decir es que el nombre está "constificado", no la variable.

Sería realmente trivial especificar e implementar: constify x; es una declaración de una referencia de referencia llamada x, que tiene el mismo tipo de base que la variable x oculta. Sigue las reglas de alcance habituales, excepto que puede definirse en el mismo ámbito que la declaración anterior x.

+0

Sí, acepto que es el nombre el que estaría constituido. Siempre se aplicaría solo al alcance actual. Y está insinuando la misma implementación usando 'const &' como di en mi pregunta. Así que sí, el compilador simplemente tendría que hacer esta transformación de programa trivial insertando la variable 'const &'. – Frank

+0

+1 Es desafortunado que las respuestas tardías rara vez reciban suficientes votos ascendentes para subir a la cima. Varias de las otras respuestas son buenas, pero esta puede ser la mejor de todas. – thb

Cuestiones relacionadas