c++ - ¿Por qué no hay un rasgo de tipo std:: is_struct?




c++11 typetraits (4)

Devuelve verdadero para ambas clases y estructuras. Sé que en C ++ son casi lo mismo, pero me gustaría saber por qué no hay una distinción entre ellos en el rasgo de tipo.

Desafortunadamente, este es un error común en C ++. A veces proviene de malentendidos fundamentales, pero otras veces proviene de una ambigüedad en inglés. Puede provenir de diagnósticos incorrectos del compilador, libros mal escritos, respuestas SO incorrectas ...

Probablemente hayas leído algo como esto:

"No hay diferencia en C ++ entre una estructura y una clase, excepto la visibilidad predeterminada de miembros y bases".

Este pasaje se puede interpretar en un sentido que es engañoso, porque las nociones de identidad e igualdad son difíciles de distinguir cuando se usan frases como "sin diferencias".

De hecho, C ++ no ha tenido estructuras desde 1985. Solo tiene clases.

El tipo de tipos que declara con la class palabra clave y la struct palabra clave son clases . Período. La struct palabras clave y las reglas de visibilidad que son las predeterminadas cuando se define una clase usando esa palabra clave, se mantuvieron solo por compatibilidad con C ... pero eso es una cuestión de sintaxis. No hace que los tipos resultantes sean realmente de un tipo diferente.

El rasgo de tipo no hace distinción porque literalmente no hay una para hacer.

He visto eso para verificar si un tipo T es una clase que puedo usar:

bool isClass = std::is_class<T>::value;

Devuelve verdadero para ambas clases y estructuras. Sé que en C ++ son casi lo mismo, pero me gustaría saber por qué no hay una distinción entre ellos en el rasgo de tipo. ¿Siempre es inútil comprobar esta diferencia, o hay alguna razón más que no entiendo?


C ++ 11 §9 / 8 ([clase] / 8) :

" Una estructura de diseño estándar es una clase de diseño estándar definida con la struct de clave de class o la clase de clave de class . Una unión de diseño estándar es una clase de diseño estándar definida con la union clave de clase .

C ++ 11 §9 / 10 ([clase] / 10) :

" Una estructura POD es una clase sin unión que es tanto una clase trivial como una clase de diseño estándar, y no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o conjunto de tales tipos). [...]

Como una estructura POD es una clase de diseño estándar, es un subconjunto de la estructura de diseño estándar . Hasta donde yo sé, este es el significado más general de struct en el estándar C ++. Y, presumiblemente, lo que está buscando es un rasgo de tipo o un conjunto de rasgos de tipo que le permita identificar una estructura de diseño estándar como tal.

Y voilà, mirando la lista de rasgos de tipo hay is_class y is_standard_layout . Cuando un tipo satisface ¹tanto es una "estructura". O más precisamente, es una estructura de diseño estándar , tal como se define en C ++ 11 §9 / 8.

Respecto a

" Me gustaría saber por qué no hay una distinción entre [clase y estructura] en el rasgo de tipo

Bueno, lo hay. Ese es el rasgo is_standard_layout .

Respecto a

" ¿Siempre es inútil comprobar esta diferencia, o hay alguna razón más que no entiendo?

No, no es inútil comprobar esta diferencia. El estándar define el diseño estándar por la razón de que es prácticamente útil. Como señala el estándar mismo,

C ++ 11 §9 / 9 ([clase] / 9) :

" [Nota: las clases de diseño estándar son útiles para comunicarse con código escrito en otros lenguajes de programación. Su diseño se especifica en 9.2. — nota final]

Notas:
is_class El rasgo is_class es verdadero para una class o struct , pero no para una union , aunque el estándar define que “una unión es una clase”. Es decir, el rasgo es más específico que la terminología general.


Otros han señalado correctamente que en C ++ las palabras clave struct y class tienen el mismo significado, excepto por la diferencia en la visibilidad de los miembros.

Si usted llama a los tipos agregados así definidos "structs" o "clases" o "weiruewzewiruz" depende de usted. En aras de la comunicación, generalmente es aconsejable seguir las convenciones establecidas, por lo que desaconsejaría "weiruewzewiruz".

También se recomienda utilizar las diferencias semánticas como guía para la elección de palabras. El uso de struct es más común para datos agregados simples que no tienen mucha lógica interna e invariantes; un uso típico sería struct point { float x; float y; }; struct point { float x; float y; }; . Tales tipos a menudo se denominan "estructuras" o "estructuras" en la literatura. No sería sorprendente si alguien que usa fprintf en C ++ se refiriera al primer argumento como un "puntero a una estructura FILE". ARCHIVO es un ejemplo de lo que Scott Meyers quiere decir en "C ++ más eficaz", elemento 34:

Es seguro asumir que ambos compiladores presentan una definición de estructura que se compila en ambos lenguajes [C y C ++ -pas].

Con respecto al lenguaje natural, la "estructura" de elección de palabras no es una coincidencia: Meyers está hablando de un agregado de datos antiguo que tiene una semántica idéntica en ambos idiomas, hasta el nivel de bits.

Con respecto al lenguaje de programación, no importaría si la definición de C ++ del agregado de datos en cuestión usara la palabra clave struct o class (con un especificador de acceso público). struct es quizás la opción más natural, porque la semántica C ++ del agregado es la semántica de una estructura C. Además, el uso de struct permite que las fuentes C y C ++ compartan más fácilmente una definición de tipo.

El estándar C ++ utiliza "struct" y "estructura" tanto en lenguaje natural como en lenguaje de programación, no solo en casos de interoperabilidad: 1.7 / 5: "Una estructura declarada como", o 3.2 / 4 struct X; // declare X as a struct type struct X; // declare X as a struct type . Lo más interesante es 9/8, sentando las bases para los criterios de interoperabilidad:

8 Una estructura de diseño estándar es una clase de diseño estándar definida con la estructura de clave de clase o la clase de clave de clase. [...]

Cómo alguien que lee esto puede afirmar que no hay estructuras en C ++ está más allá de mí. Esto claramente no es un error de edición porque los términos "estructura" y "clase" se establecen explícitamente en relación entre sí.

Sin embargo, más interesantes que las elecciones de palabras y las cuestiones de gusto son las diferencias manifiestas y comprobables. ¿En qué circunstancias es un agregado de C ++ comparable y compatible con una struct C? ¿Quizás esta pregunta estaba detrás de tu pregunta? El diseño estándar mencionado en la cita es el criterio. Se detalla en 9/7 y esencialmente prescribe que

  • solo una clase en una jerarquía de herencia puede tener definiciones de miembro de datos no estáticas (probablemente porque el estándar no quiere especificar el orden de los elementos de datos definidos en diferentes niveles en dicha jerarquía);
  • no se permiten funciones virtuales o clases base virtuales (debido a los datos de instancia adicionales necesarios para la información de tiempo de ejecución);
  • todos los miembros tienen el mismo "control de acceso" (público, protegido o privado; probablemente porque una implementación es libre de ordenar por control de acceso).

El estándar luego dice

9 [Nota: las clases de diseño estándar son útiles para comunicarse con código escrito en otros lenguajes de programación. Su diseño se especifica en 9.2. — nota final]

Por supuesto, una definición de estructura que compila en C cumple con estos criterios, de ahí la afirmación de Scott Meyers. FILE de stdio.h es un ejemplo destacado, no del todo trivial. Tenga en cuenta que el estándar no garantiza porque el diseño del objeto depende de la implementación y puede cambiar solo con una opción del compilador.

Se puede comprobar si una clase tiene un diseño estándar con el tipo rasgo std::is_standard_layout<T> . El siguiente programa, inspirado en un ejemplo sobre cppreference , verifica los casos principales establecidos en el estándar.

#include <cstdio>
#include <typeinfo>
#include <type_traits>

using namespace std;

struct funcOnlyT // fine
{
    int f();
};

class podT {   // "class" is ok
    int m1;
    int m2;
};

struct badAccessCtrlT { // bad: public/private
    int m1;
private:
    int m2;
};

struct polymorphicT {  // bad: polymorphic
    int m1;
    int m2;
    virtual void foo();
};


struct inheritOkT: podT // ok: inheritance, data only on one level
{
    int f();
};


struct inheritPlusDataT: podT // bad: inheritance, data on 2 levels
{
    int m3;
};

template<typename T1, typename T2>
struct templT     // ok with std layout types T1, T2
{
    T1 m1;
    T2 m2;
};

// print type "name" and whether it's std layout
template<typename T>
void printIsStdLayout()
{
    printf("%-20s: %s\n", 
            typeid(T).name(),
            std::is_standard_layout<T>::value 
                ? "is std layout" 
                : "is NOT std layout");
}

int main()
{
    printIsStdLayout<funcOnlyT>();
    printIsStdLayout<podT>();
    printIsStdLayout<badAccessCtrlT>();
    printIsStdLayout<polymorphicT>();
    printIsStdLayout<inheritOkT>();
    printIsStdLayout<inheritPlusDataT>();
    printIsStdLayout<templT<int, float> >();
    printIsStdLayout<FILE>();
}

Sesión de muestra:

$ g++ -std=c++11 -Wall -o isstdlayout isstdlayout.cpp && ./isstdlayout
9funcOnlyT          : is std layout
4podT               : is std layout
14badAccessCtrlT    : is NOT std layout
12polymorphicT      : is NOT std layout
10inheritOkT        : is std layout
16inheritPlusDataT  : is NOT std layout
6templTIifE         : is std layout
9__sFILE64          : is std layout

Son lo mismo. La única diferencia (visibilidad predeterminada del miembro) existe solo en el momento de la compilación. De lo contrario, no hay ninguna diferencia entre struct y class .

ETA: Lo que probablemente desee es std::is_pod , que le dirá si su clase es un "tipo de datos antiguo". Gran parte de la discusión y el comentario sobre esta pregunta parece indicar que esto es lo que realmente quieren aquellos que piensan que debería haber una distinción.







typetraits