c++ - significado - tipos de variables en java ejemplos
¿Cómo funcionan las variables en línea? (3)
La primera oración de la propuesta:
" El especificador en
inline
se puede aplicar tanto a las variables como a las funciones.
El efecto garantizado de
inline
aplicado a una función es permitir que la función se defina de forma idéntica, con enlaces externos, en múltiples unidades de traducción.
Para la práctica, eso significa definir la función en un encabezado, que se puede incluir en varias unidades de traducción.
La propuesta extiende esta posibilidad a las variables.
Entonces, en términos prácticos, la propuesta (ahora aceptada) le permite usar la palabra clave en
inline
para definir una variable de alcance de espacio de nombres de enlace externo
const
, o cualquier miembro de datos de clase
static
, en un archivo de encabezado, de modo que las definiciones múltiples que resultan cuando ese encabezado está incluido en varias unidades de traducción, está bien con el enlazador, solo elige
una
de ellas.
Hasta e incluyendo C ++ 14, la maquinaria interna para esto ha estado allí, con el fin de admitir variables
static
en las plantillas de clase, pero no había una manera conveniente de usar esa maquinaria.
Había que recurrir a trucos como
template< class Dummy >
struct Kath_
{
static std::string const hi;
};
template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";
using Kath = Kath_<void>; // Allows you to write `Kath::hi`.
Desde C ++ 17 en adelante, creo que uno puede escribir solo
struct Kath
{
static std::string const hi;
};
inline std::string const Kath::hi = "Zzzzz..."; // Simpler!
... en un archivo de encabezado.
La propuesta incluye la redacción
” Un miembro de datos estáticos en línea se puede definir en la definición de clase y puede especificar un inicializador de paréntesis o igual. Si el miembro se declara con el especificador
constexpr
, se puede volver a declarar en el ámbito del espacio de nombres sin inicializador (este uso está en desuso; consulte DX). Las declaraciones de otros miembros de datos estáticos no deben especificar un inicializador de llave o igual
... lo que permite que lo anterior se simplifique aún más a solo
struct Kath
{
static inline std::string const hi = "Zzzzz..."; // Simplest!
};
... como señaló TC en un comentario a esta respuesta.
Además, el especificador
constexpr
implica en
inline
para miembros de datos estáticos, así como funciones.
Notas:
¹ Para una función en
inline
también tiene un efecto indirecto sobre la optimización, que el compilador debería preferir reemplazar las llamadas de esta función con la sustitución directa del código de máquina de la función.
Esta sugerencia puede ser ignorada.
En la reunión de estándares 2016 de Oulu ISO C ++, el comité de estándares votó una propuesta llamada Inline Variables en C ++ 17.
En términos simples, ¿qué son las variables en línea, cómo funcionan y para qué sirven? ¿Cómo deben declararse, definirse y utilizarse las variables en línea?
Las variables en línea son muy similares a las funciones en línea. Le indica al vinculador que solo debe existir una instancia de la variable, incluso si la variable se ve en varias unidades de compilación. El vinculador debe asegurarse de que no se creen más copias.
Las variables en línea se pueden usar para definir globales en bibliotecas de encabezado solamente. Antes de C ++ 17, tenían que usar soluciones alternativas (funciones en línea o hacks de plantillas).
Por ejemplo, una solución alternativa es usar el singleton de Meyer con una función en línea:
inline T& instance()
{
static T global;
return global;
}
Hay algunos inconvenientes con este enfoque, principalmente en términos de rendimiento. Las soluciones de plantilla pueden evitar esta sobrecarga, pero es fácil equivocarse.
Con variables en línea, puede declararlo directamente (sin obtener un error de enlazador de definición múltiple):
inline T global;
Además de las bibliotecas de encabezado solamente, existen otros casos en los que las variables en línea pueden ayudar. Nir Friedman cubre este tema en su charla en CppCon: Lo que los desarrolladores de C ++ deben saber sobre los globales (y el enlazador) . La parte sobre las variables en línea y las soluciones comienza a los 18m9s .
Para resumir, si necesita declarar variables globales que se comparten entre las unidades de compilación, declararlas como variables en línea en el archivo de encabezado es sencillo y evita los problemas con soluciones alternativas anteriores a C ++ 17.
(Todavía hay casos de uso para el singleton de Meyer, por ejemplo, si desea explícitamente tener una inicialización diferida).
Ejemplo ejecutable mínimo
Esta increíble característica de C ++ 17 nos permite:
- utilice convenientemente una sola dirección de memoria para cada constante
-
almacenarlo como
constexpr
: ¿Cómo declarar constexpr extern? - hazlo en una sola línea desde un encabezado
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
Compilar y ejecutar:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
Ver también: ¿Cómo funcionan las variables en línea?
C ++ estándar en variables en línea
El estándar C ++ garantiza que las direcciones serán las mismas. C ++ 17 N4659 borrador estándar 10.1.6 "El especificador en línea":
6 Una función o variable en línea con enlace externo tendrá la misma dirección en todas las unidades de traducción.
cppreference
https://en.cppreference.com/w/cpp/language/inline
explica que si no se proporciona
static
, entonces tiene un enlace externo.
Implementación variable en línea de GCC
Podemos observar cómo se implementa con:
nm main.o notmain.o
que contiene:
main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
y
man nm
dice acerca de
u
:
"u" El símbolo es un símbolo global único. Esta es una extensión de GNU para el conjunto estándar de enlaces de símbolos ELF. Para dicho símbolo, el enlazador dinámico se asegurará de que en todo el proceso solo haya un símbolo con este nombre y tipo en uso.
entonces vemos que hay una extensión ELF dedicada para esto.
Pre-C ++ 17:
extern const
Antes de C ++ 17, y en C, podemos lograr un efecto muy similar con una
extern const
, lo que conducirá a que se use una única ubicación de memoria.
Las desventajas sobre en
inline
son:
-
no es posible hacer la variable
constexpr
con esta técnica, solo eninline
permite eso: ¿Cómo declarar constexpr extern? - es menos elegante ya que debe declarar y definir la variable por separado en el encabezado y el archivo cpp
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
Pre-C ++ 17 encabezado solo alternativas
Estos no son tan buenos como la solución
extern
, pero funcionan y solo ocupan una única ubicación de memoria:
Una función
constexpr
, porque
constexpr
implica en
inline
e
inline
permite (fuerza) que la definición aparezca en cada unidad de traducción
:
constexpr int shared_inline_constexpr() { return 42; }
y apuesto a que cualquier compilador decente alineará la llamada.
También puede usar una variable entera estática
const
o
constexpr
como en:
#include <iostream>
struct MyClass {
static constexpr int i = 42;
};
int main() {
std::cout << MyClass::i << std::endl;
// undefined reference to `MyClass::i''
//std::cout << &MyClass::i << std::endl;
}
pero no puede hacer cosas como tomar su dirección, o de lo contrario se usará odr, vea también: https://en.cppreference.com/w/cpp/language/static "Miembros estáticos constantes" y Definiendo datos estáticos constexpr miembros
do
En C la situación es la misma que C ++ pre C ++ 17, he subido un ejemplo en: ¿Qué significa "estático" en C?
La única diferencia es que en C ++,
const
implica
static
para los globales, pero no en C:
C ++ semántica de `static const` vs` const`
¿Alguna forma de alinearlo completamente?
TODO: ¿hay alguna forma de alinear completamente la variable, sin usar memoria alguna?
Muy parecido a lo que hace el preprocesador.
Esto requeriría de alguna manera:
- prohibir o detectar si se toma la dirección de la variable
- agregue esa información a los archivos de objetos ELF y deje que LTO la optimice
Relacionado:
Probado en Ubuntu 18.10, GCC 8.2.0.