Comparación de const y constexpr

Este artículo se transfirió del blog del Gran Dios , adore aquí.

constante

En c ++, const se puede usar para modificar variables y funciones, y tiene diferentes significados para diferentes propósitos. La siguiente es una explicación

semántica constante

El propósito de const en C ++ es asegurar la naturaleza constante del objeto a través del compilador, lo que obliga al compilador a tratar todas las operaciones que puedan violar la naturaleza constante del objeto const como un error.
La constancia de los objetos se puede dividir en dos tipos: constancia física (es decir, no se puede cambiar cada bit) y constancia lógica (es decir, el comportamiento del objeto permanece sin cambios). La constante física se usa en C ++, como en el siguiente ejemplo:

struct A {
    
    
    int *ptr;
};
int k = 5, r = 6;
const A a = {
    
    &k};
a.ptr = &r;  // error
*a.ptr = 7;  // no error

a es un objeto constante, la asignación de cualquier miembro de a se considerará un error, pero si no cambia ptr, pero cambia el objeto al que apunta ptr, el compilador no informará de un error. ¡Esto en realidad viola la constancia lógica, porque el desempeño de A ha cambiado!

Otra característica de la constante lógica es que puede haber algunos dominios invisibles para el usuario en el objeto const, y cambiarlos no violará la constante lógica. Ejemplos en C ++ efectivo son:

class CTextBlock {
    
     
public: 
    ... 
    std::size_t length() const; 
private: 
    char *pText; 
    std::size_t textLength;            // last calculated length of textblock 
    bool lengthIsValid;                // whether length is currently valid 
};

Cada vez que el objeto CTextBlock llama al método length, almacena en caché la longitud actual en el miembro textLength y el objeto lengthIsValid representa la eficacia de la caché. En este escenario, si se cambian textLength y lengthIsValid, no viola la constante lógica del objeto CTextBlock, pero debido a que se cambian algunos bits del objeto, el compilador lo bloqueará. Para resolver este problema en C ++, se agrega la palabra clave mutable.

Esta sección resume: La semántica de const en C ++ es para asegurar constantes físicas, pero una parte de las constantes lógicas se puede soportar mediante la palabra clave mutable.

variable modificada const

Como se mencionó en la sección anterior, la semántica de modificar una variable con const requiere que el compilador evite todas las asignaciones a la variable. Por lo tanto, el valor inicial de la variable const debe proporcionarse cuando se inicializa:

const int i;
i = 5;  // error
const int j = 10;  // ok

Este valor inicial puede ser un valor determinado en tiempo de compilación o un valor determinado en tiempo de ejecución. Si le da un valor inicial en tiempo de compilación a una variable constante de tipo entero, puede usar esta variable como la longitud de la matriz al declararla:

const int COMPILE_CONST = 10;
const int RunTimeConst = cin.get();
int a1[COMPLIE_CONST];  // ok in C++ and error in C
int a2[RunTimeConst];  // error in C++

Porque el compilador de C ++ puede reemplazar directamente la constante de tiempo de compilación en la longitud de la matriz con su valor literal, que es equivalente al reemplazo automático de macro. (La verificación de gcc descubrió que solo se reemplazó directamente la longitud de la matriz y que los otros lugares asignados con COMPILE_CONST no se reemplazaron).

La variable const del dominio del archivo está visible en el archivo de forma predeterminada. Si necesita usar la variable const M en a.cpp en b.cpp, debe agregar extern en la inicialización de M:

// a.cpp
extern const int M = 20;
 
// b.cpp
extern const int M;

Generalmente se cree que poner la definición de la variable en el archivo .h hará que todos los archivos .cpp que incluyen el archivo .h tengan la definición de esta variable, lo que provocará conflictos durante la vinculación. Pero es posible poner la definición de la variable const en el archivo .h. El compilador colocará esta variable en el espacio de nombres anónimo de cada archivo .cpp, por lo que es una variable diferente y no causará conflictos de enlaces. (Nota: Pero si el valor inicial de la cantidad constante en el archivo de encabezado depende de una función determinada, y el valor de retorno de cada llamada a esta función no es fijo, causará que el valor de la cantidad constante se vea en diferentes unidades de compilación para ser desigual. Adivina: en este momento, usar la cantidad constante como un miembro estático de una determinada clase puede resolver este problema).

punteros y referencias modificados const

Cuando const modifica una referencia, su significado es el mismo que modifica una variable. Pero cuando const modifica los punteros, las reglas son un poco más complicadas.

En pocas palabras, el tipo de una variable de puntero se puede dividir en dos partes de acuerdo con el '*' más cercano a la izquierda del nombre de la variable. La parte derecha representa la naturaleza de la variable de puntero y la parte izquierda representa la naturaleza de la variable de puntero. elemento al que apunta:

const int *p1;  // p1 is a non-const pointer and points to a const int
int * const p2;  // p2 is a const pointer and points to a non-const int
const int * const p3;  // p3 is a const pointer and points to a const int
const int *pa1[10];  // pa1 is an array and contains 10 non-const pointer point to a const int
int * const pa2[10];  // pa2 is an array and contains 10 const pointer point to a non-const int
const int (* p4)[10];  // p4 is a non-const pointer and points to an array contains 10 const int
const int (*pf)();  // pf is a non-const pointer and points to a function which has no arguments and returns a const int
...

Las reglas de interpretación para punteros const son casi las mismas ...

El puntero en sí es constante, lo que significa que no se puede asignar el puntero y el puntero es constante, lo que significa que no se puede asignar el puntero. Por lo tanto, una referencia se puede considerar como un puntero que es const en sí mismo, y una referencia const es un puntero const Type * const.

Los punteros a const no se pueden asignar a punteros que apuntan a no const, y las referencias const no se pueden asignar a referencias no const, pero lo contrario no es un problema. Esto también es para asegurar que la semántica const no se destruya.

Puede usar const_cast para eliminar la propiedad const de un puntero o referencia, o usar static_cast para agregar la propiedad const a un puntero o referencia no constante:

int i;
const int *cp = &i;
int *p = const_cast<int *>(cp);
const int *cp2 = static_cast<const int *>(p);  // here the static_cast is optional

El puntero this en una clase de C ++ es un puntero cuyo propio es const, y el puntero this en el método const de una clase es un puntero cuyo punto y él son ambos const.

Variables de miembro constante en la clase

Las variables miembro const de la clase se pueden dividir en dos tipos: constantes no estáticas y constantes estáticas.

Constante no estática:

Las constantes no estáticas de la clase deben inicializarse en la lista de inicialización del constructor, porque los miembros no estáticos de la clase se construyen antes de ingresar al cuerpo de la función del constructor, y las constantes const deben inicializarse durante la construcción. El compilador bloqueará las asignaciones.

class B {
    
    
public:
    B(): name("aaa") {
    
    
        name = "bbb";  // error
    }
private:
    const std::string name;
};

constante estática:

La constante estática se declara directamente en la clase, pero se requiere la única definición y valor inicial fuera de la clase. El método común es incluir la definición de la constante estática de la clase en el .cpp correspondiente:

// a.h
class A {
    
    
    ...
    static const std::string name;
};
 
// a.cpp
const std::string A::name("aaa");

Un caso especial es que si el tipo de la constante estática es un tipo entero integrado, como char, int, size_t, etc., entonces el valor inicial se puede dar directamente en la clase y no es necesario definir fuera de la clase. El compilador reemplazará directamente esta constante estática con el valor inicial correspondiente, que es equivalente al reemplazo de la macro. Pero si usamos esta constante estática como una variable normal en el código, como tomar su dirección en lugar de simplemente usar su valor como una macro, entonces aún necesitamos proporcionar una definición fuera de la clase, pero no se necesita un valor inicial (porque ya existe en la declaración).

// a.h
class A {
    
    
    ...
    static const int SIZE = 50; 
};
 
// a.cpp
const int A::SIZE = 50;  // if use SIZE as a variable, not a macro

función modificada const

En C ++, const se puede usar para modificar la función miembro no estática de una clase, y su semántica es para asegurar la constidad del objeto correspondiente a la función. En funciones miembro const, todas las operaciones que puedan violar la constness de este puntero (este puntero en funciones miembro const es un puntero constante doble) serán bloqueadas, como la asignación a otras variables miembro, llamar a sus métodos no constantes y llamar objetos El método no constante en sí. Pero cualquier operación realizada en una variable miembro declarada como mutable no será bloqueada. Aquí se garantiza una cierta constante lógica.

Además, cuando const modifica la función, también participa en la sobrecarga de la función, es decir, cuando se llama a un método a través de un objeto const, un puntero const o una referencia, se llama primero al método const.

class A {
    
    
public:
    int &operator[](int i) {
    
    
        ++cachedReadCount;
        return data[i];
    }
    const int &operator[](int i) const {
    
    
        ++size; // !error
        --size; // !error
        ++cachedReadCount; // ok
        return data[i];
    }
private:
    int size;
    mutable cachedReadCount;
    std::vector<int> data;
};
 
A &a = ...;
const A &ca = ...;
int i = a[0]; // call operator[]
int j = ca[0]; // call const operator[]
a[0] = 2; // ok
ca[0] = 2; // !error

En este ejemplo, si las dos versiones de operator [] tienen básicamente el mismo código, considere llamar a otra función en una de las funciones para lograr la reutilización del código (consulte C ++ efectivo). Aquí solo podemos usar la versión no const para llamar a la versión const.

int &A::operator[](int i) {
    
    
    return const_cast<int &>(static_cast<const A &>(*this).operator[](i));
}

Entre ellos, para evitar llamarse a sí mismo para causar un bucle infinito, primero convierta * this a const A &, lo que se puede hacer usando static_cast. Después de obtener el valor de retorno del operador const [], debe eliminar manualmente su const, lo que se puede hacer usando const_cast. En términos generales, no se recomienda const_cast, pero aquí sabemos claramente que el objeto con el que estamos tratando en realidad no es const, por lo que es seguro usar const_cast aquí.

constexpr

constexpr es una nueva palabra clave en C ++ 11, y su semántica es "expresión constante", es decir, una expresión que se puede evaluar en tiempo de compilación. La expresión constante más básica es el resultado devuelto por palabras clave como valor literal o dirección de función / variable global o sizeof, mientras que otras expresiones constantes se obtienen mediante expresiones básicas a través de varias operaciones deterministas. El valor constexpr se puede usar para enumeración, cambio, longitud de matriz y otras ocasiones.

La variable modificada por constexpr debe ser evaluable en tiempo de compilación y la función modificada debe devolver constexpr cuando todos sus parámetros son constexpr.

constexpr int Inc(int i) {
    
    
    return i + 1;
}
 
constexpr int a = Inc(1); // ok
constexpr int b = Inc(cin.get()); // !error
constexpr int c = a * 2 + 1; // ok

constexpr también se puede usar para modificar el constructor de una clase, es decir, para asegurar que si los parámetros proporcionados al constructor son constexpr, entonces todos los miembros en el objeto generado serán constexpr, que también es un objeto constexpr, y puede ser utilizado para varios tipos de Donde se puede utilizar constexpr. Tenga en cuenta que el constructor constexpr debe tener un cuerpo de función vacío, es decir, la inicialización de todas las variables miembro se coloca en la lista de inicialización.

struct A {
    
    
    constexpr A(int xx, int yy): x(xx), y(yy) {
    
    }
    int x, y;
};
 
constexpr A a(1, 2);
enum {
    
    SIZE_X = a.x, SIZE_Y = a.y};

Beneficios de constexpr:

  1. Es una restricción muy fuerte garantizar mejor que no se destruya la semántica correcta del programa.
  2. El traductor puede realizar optimizaciones muy grandes en el código constexpr en tiempo de compilación, como reemplazar directamente todas las expresiones constexpr utilizadas con el resultado final.
  3. En comparación con las macros, no hay gastos generales adicionales, pero es más seguro y confiable.

Supongo que te gusta

Origin blog.csdn.net/qq_24649627/article/details/110631381
Recomendado
Clasificación