Cadena constante estática (miembro de la clase)

445

Me gustaría tener una constante estática privada para una clase (en este caso, una fábrica de formas).

Me gustaría tener algo por el estilo.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

Desafortunadamente, recibo todo tipo de errores del compilador de C ++ (g ++), como:

ISO C ++ prohíbe la inicialización del miembro 'RECTANGLE'

Inicialización en clase no válida del miembro de datos estáticos de tipo no integral 'std :: string'

error: haciendo estático 'RECTANGLE'

Esto me dice que este tipo de diseño de miembro no cumple con el estándar. ¿Cómo se tiene una constante literal privada (o quizás pública) sin tener que usar una directiva #define (¡quiero evitar la fealdad de la globalidad de los datos!)

Cualquier ayuda es apreciada.

lb.
fuente
15
¡Gracias por todas tus excelentes respuestas! ¡Viva TAN!
lb.
¿Puede alguien decirme qué es un tipo 'integral'? Muchas gracias.
lb.
1
Los tipos integrales se refieren a los tipos que representan números enteros. Ver publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
bleater
La cadena estática privada en su fábrica no es una buena solución: considere que sus clientes de fábrica tendrán que saber qué formas son compatibles, por lo que en lugar de mantenerla en estática privada, colóquelas en un espacio de nombres separado como static const std :: string RECTANGLE = "Rectangle ".
LukeCodeBaker
si su clase es una clase de plantilla, consulte stackoverflow.com/q/3229883/52074
Trevor Boyd Smith el

Respuestas:

470

Debe definir su miembro estático fuera de la definición de clase y proporcionar el inicializador allí.

primero

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

y entonces

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

La sintaxis que originalmente intentaba usar (inicializador dentro de la definición de clase) solo se permite con tipos integrales y enum.


A partir de C ++ 17, tiene otra opción, que es bastante similar a su declaración original: variables en línea

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

No se necesita una definición adicional.

O en lugar de constusted puede declararlo constexpren esta variante. Explícito inlineya no sería necesario, ya que constexprimplica inline.

Hormiga
fuente
8
Además, si no se requiere el uso de una cadena STL, también podría definir un const char *. (menos gastos generales)
KSchmidt
50
No estoy seguro de que siempre sea menos sobrecarga, depende del uso. Si se pretende que este miembro se pase como un argumento a las funciones que toman cadena constante &, se creará temporalmente para cada llamada frente a la creación de un objeto de cadena durante la inicialización. La sobrecarga de IMHO para crear un objeto de cadena estática es insignificante.
Tadeusz Kopec
23
Prefiero usar std :: string todo el tiempo también. La sobrecarga es insignificante, pero tienes muchas más opciones y es mucho menos probable que escribas cosas tontas como "magic" == A :: RECTANGLE solo para comparar su dirección ...
Matthieu M.
99
la char const*tiene la bondad que se inicia antes de que termine toda la inicialización dinámica. Entonces, en el constructor de cualquier objeto, puede confiar en RECTANGLEque ya se haya inicializado.
Johannes Schaub - litb
8
@cirosantilli: Porque desde el principio de los tiempos en los inicializadores de C ++ eran partes de definiciones , no declaraciones . Y la declaración del miembro de datos dentro de la clase es solo eso: una declaración. (Por otro lado, se hizo una excepción para los miembros const integral y enum, y en C ++ 11 - para los miembros const de tipos literales .)
AnT
153

En C ++ 11 puedes hacer ahora:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
abismo.7
fuente
30
Lamentablemente, esta solución no funciona para std :: string.
Hola
2
Tenga en cuenta que 1. esto solo funciona con literales y 2. esto no es conforme estándar, aunque Gnu / GCC cumple multas, otros compiladores arrojarán un error. La definición tiene que estar en el cuerpo.
ManuelSchneid3r
2
@ ManuelSchneid3r ¿Cómo es exactamente esto "no conforme a la norma"? A mí me parece una inicialización ortopédica C ++ 11 estándar de pantano .
underscore_d
3
@rvighne, no, eso es incorrecto. constexprimplica constpara var, no para escribir puntos. Es decir, static constexpr const char* constes lo mismo que static constexpr const char*, pero no es lo mismo que static constexpr char*.
midenok
2
@ abyss.7 - Gracias por tu respuesta, y tengo otra por favor: ¿Por qué tiene que ser estática?
Guy Avraham el
34

Dentro de las definiciones de clase solo puede declarar miembros estáticos. Tienen que ser definidos fuera de la clase. Para las constantes integrales en tiempo de compilación, el estándar hace la excepción de que puede "inicializar" miembros. Sin embargo, todavía no es una definición. Tomar la dirección no funcionaría sin definición, por ejemplo.

Me gustaría mencionar que no veo el beneficio de usar std :: string sobre const char [] para constantes . std :: string es agradable y todo pero requiere una inicialización dinámica. Entonces, si escribes algo como

const std::string foo = "hello";

en el ámbito del espacio de nombres, el constructor de foo se ejecutará justo antes de la ejecución de los inicios principales y este constructor creará una copia de la constante "hola" en la memoria del montón. A menos que realmente necesite RECTANGLE para ser un std :: string, también podría escribir

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

¡Allí! Sin asignación de montón, sin copia, sin inicialización dinámica.

Saludos, s.

sellibitze
fuente
1
Esta es la respuesta previa a C ++ 11. Use C ++ estándar y use std :: string_view.
1
C ++ 11 no tiene std :: string_view.
Lukas Salich
17

Esto es solo información adicional, pero si realmente desea la cadena en un archivo de encabezado, intente algo como:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Aunque dudo que sea recomendable.

GManNickG
fuente
Eso se ve genial :) - ¿Supongo que tienes experiencia en otros idiomas además de c ++?
lb.
55
No lo recomendaría Hago esto con frecuencia. Funciona bien y lo encuentro más obvio que poner la cadena en el archivo de implementación. Sin embargo, los datos reales de std :: string todavía se encuentran en el montón. Devolvería un const char *, en cuyo caso no necesita declarar la variable estática, por lo que la declaración ocuparía menos espacio (en cuanto al código). Solo es cuestión de gustos.
Zoomulator
15

En C ++ 17 puede usar variables en línea :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Tenga en cuenta que esto es diferente de la respuesta de abyss.7 : este define un std::stringobjeto real , no unconst char*

Oz Solomon
fuente
¿No crees que usar inlinecreará muchos duplicados?
shuva
1
@shuva No, la variable no se duplicará .
zett42
8

Para usar esa sintaxis de inicialización en clase, la constante debe ser una constante estática de tipo integral o de enumeración inicializada por una expresión constante.

Esta es la restricción. Por lo tanto, en este caso necesita definir variables fuera de la clase. refiera la respuesta de @AndreyT

aJ.
fuente
7

Las variables estáticas de clase se pueden declarar en el encabezado pero se deben definir en un archivo .cpp. Esto se debe a que solo puede haber una instancia de una variable estática y el compilador no puede decidir en qué archivo de objeto generado colocarlo, por lo que debe tomar la decisión.

Para mantener la definición de un valor estático con la declaración en C ++ 11, se puede utilizar una estructura estática anidada. En este caso, el miembro estático es una estructura y debe definirse en un archivo .cpp, pero los valores están en el encabezado.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

En lugar de inicializar miembros individuales, toda la estructura estática se inicializa en .cpp:

A::_Shapes A::shape;

Se accede a los valores con

A::shape.RECTANGLE;

o - dado que los miembros son privados y están destinados a ser utilizados solo desde A - con

shape.RECTANGLE;

Tenga en cuenta que esta solución aún adolece del problema del orden de inicialización de las variables estáticas. Cuando se usa un valor estático para inicializar otra variable estática, la primera puede no inicializarse todavía.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

En este caso, los encabezados de las variables estáticas contendrán {""} o {".h", ".hpp"}, según el orden de inicialización creado por el vinculador.

Como mencionó @ abyss.7, también podría usar constexprsi el valor de la variable se puede calcular en tiempo de compilación. Pero si declara sus cadenas con static constexpr const char*y su programa usa lo std::stringcontrario, habrá una sobrecarga porque se std::stringcreará un nuevo objeto cada vez que use una constante de este tipo:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}
Marko Mahnič
fuente
Respuesta bien preparada Marko. Dos detalles: uno no necesita archivos cpp para miembros de clase estáticos, y también use std :: string_view para cualquier tipo de constantes.
4

El estándar actual solo permite dicha inicialización para los tipos integrales constantes estáticos. Entonces debes hacer lo que AndreyT explicó. Sin embargo, eso estará disponible en el próximo estándar a través de la sintaxis de inicialización del nuevo miembro .

Leandro TC Melo
fuente
4

posible solo haz:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

o

#define RECTANGLE "rectangle"
chikuba
fuente
11
Usar #define cuando se puede usar una constante escrita es simplemente incorrecto.
Artur Czajka
Su primer ejemplo es básicamente una buena solución si no tiene constexprpero no puede hacer una función estática const.
Frank Puffer
Esta solución debe ser evitada. Crea una nueva cadena en cada invocación. Esto sería mejor:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon
¿Por qué usar el contenedor completo como valor de retorno? Use std :: string_vew ... su contenido seguirá siendo válido en este caso. incluso mejor use literales de cadena para hacer y devolver la vista de cadena ... y por último pero no menos importante, el valor de retorno constante no tiene significado o efecto aquí ... sí, y tenga esto como en línea, no estático, en algún encabezado en nombrado espacio de nombres ... y por favor haga que sea constexpr
4

Puede optar por la const char*solución mencionada anteriormente, pero si necesita una secuencia todo el tiempo, tendrá una sobrecarga.
Por otro lado, la cadena estática necesita una inicialización dinámica, por lo tanto, si desea utilizar su valor durante la inicialización de otra variable global / estática, puede encontrar el problema del orden de inicialización. Para evitar eso, lo más barato es acceder al objeto de cadena estática a través de un getter, que verifica si su objeto está inicializado o no.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

Recuerde usar solo A::getS(). Debido a que cualquier subproceso solo puede iniciarse main()y A_s_initializedse inicializa antes main(), no necesita bloqueos incluso en un entorno multiproceso. A_s_initializedes 0 de forma predeterminada (antes de la inicialización dinámica), por lo que si usa getS()antes de que se inicialice s, llamará a la función init de forma segura.

Por cierto, en la respuesta anterior: " static const std :: string RECTANGLE () const ", las funciones estáticas no pueden serconst porque no pueden cambiar el estado si algún objeto de todos modos (no hay este puntero).

usuario2806882
fuente
4

Avance rápido a 2018 y C ++ 17.

  • no use std :: string, use std :: string_view literales
  • tenga en cuenta el 'constexpr' a continuación. Este también es un mecanismo de "tiempo de compilación".
  • no en línea no significa repetición
  • no se necesitan archivos cpp para esto
  • static_assert 'funciona' solo en tiempo de compilación

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

Arriba está un ciudadano C ++ estándar apropiado y legal. Puede involucrarse fácilmente en cualquiera y todos los algoritmos std ::, contenedores, utilidades y demás. Por ejemplo:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

Disfruta del estándar C ++


fuente
Use std::string_viewpara constantes solo si usa string_viewparámetros en todas sus funciones. Si alguna de sus funciones usa un const std::string&parámetro, se creará una copia de una cadena cuando pase una string_viewconstante a través de ese parámetro. Si sus constantes son de tipo, std::stringlas copias no se crearán ni para const std::string&parámetros ni para std::string_viewparámetros.
Marko Mahnič el
Buena respuesta, pero curioso por qué string_view se devuelve desde una función? Este tipo de truco fue útil antes de que las inlinevariables llegaran a C ++ 17 con su semántica ODR. Pero string_view también es C ++ 17, por lo que solo constexpr auto some_str = "compile time"sv;hace el trabajo (y en realidad, no es una variable, es constexpr, por inlinelo tanto, está implícito; si tiene una variable, es decir, no constexpr), inline auto some_str = "compile time"sv;lo hará, aunque, por supuesto, un ámbito de espacio de nombres variable, que es esencialmente una variable global, rara vez sería una buena idea).
Mentalidad de pérdida