¿Características ocultas de C ++? [cerrado]

114

¿No le gusta C ++ cuando se trata de las "características ocultas de" línea de preguntas? Pensé que lo tiraría por ahí. ¿Cuáles son algunas de las características ocultas de C ++?

Craig H
fuente
@Devtron - He visto algunos errores asombrosos (es decir, comportamiento inesperado) vendidos como características. De hecho, la industria de los juegos en realidad intenta que esto suceda hoy en día y lo llama "jugabilidad emergente" (también, mira "TK Surfing" de Psi-Ops, fue puramente un error, luego lo dejaron como está y es uno de los mejores características del juego en mi humilde opinión)
Grant Peters
5
@Laith J: No mucha gente ha leído el estándar ISO C ++ de 786 páginas de principio a fin, pero supongo que sí, y lo ha conservado todo, ¿verdad?
j_random_hacker
2
@Laith, @j_random: Vea mi pregunta "¿Qué es una broma de programador, cómo la reconozco y cuál es la respuesta adecuada?" En stackoverflow.com/questions/1/you-have-been-link-rolled .

Respuestas:

308

La mayoría de los programadores de C ++ están familiarizados con el operador ternario:

x = (y < 0) ? 10 : 20;

Sin embargo, no se dan cuenta de que se puede usar como un lvalue:

(a == 0 ? a : b) = 1;

que es la abreviatura de

if (a == 0)
    a = 1;
else
    b = 1;

Úselo con precaución :-)

2 revoluciones
fuente
11
Muy interesante. Sin embargo, puedo ver eso haciendo un código ilegible.
Jason Baker
112
¡Ay! (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers
52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky
12
No sé si es específica del CCG, pero me sorprendió encontrar esto también funcionó: (value ? function1 : function2)().
Chris Burt-Brown
3
@Chris Burt-Brown: No, eso debería funcionar en todas partes si tienen el mismo tipo (es decir, sin argumentos predeterminados) function1y function2se convierten implícitamente en punteros de función, y el resultado se convierte implícitamente de nuevo.
MSalters
238

Puede poner URI en la fuente C ++ sin errores. Por ejemplo:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}
Ben
fuente
41
Pero sospecho que solo uno por función. :)
Constantin
51
@jpoh: http seguido de dos puntos se convierte en una "etiqueta" que se usa en una declaración goto más adelante. obtienes esa advertencia de tu compilador porque no se usa en ninguna instrucción goto en el ejemplo anterior.
utku_karatas
9
¡Puede agregar más de uno siempre que tengan protocolos diferentes! ftp.microsoft.com gopher: //aerv.nl/1 y así sucesivamente ...
Daniel Earwicker
4
@Pavel: un identificador seguido de dos puntos es una etiqueta (para usar con goto, que C ++ sí tiene). Todo lo que siga a dos barras es un comentario. Por lo tanto, con http://stackoverflow.com, httpes una etiqueta (en teoría, podría escribir goto http;) y //stackoverflow.comes solo un comentario de final de línea. Ambos son C ++ legal, por lo que la construcción se compila. No hace nada vagamente útil, por supuesto.
David Thornley
8
Desafortunadamente goto http;, en realidad no sigue la URL. :(
Yakov Galka
140

Aritmética de puntero.

Los programadores de C ++ prefieren evitar los punteros debido a los errores que pueden introducirse.

¿Sin embargo, el C ++ más genial que he visto? Literales analógicos.

Anonymouse
fuente
11
¿Evitamos los punteros por errores? ¡Los punteros son básicamente todo lo que trata la codificación dinámica de C ++!
Nick Bedford
1
Los literales analógicos son excelentes para las entradas de concurso de C ++ ofuscadas, especialmente el tipo ASCII-art.
Synetech
119

Estoy de acuerdo con la mayoría de las publicaciones: C ++ es un lenguaje de múltiples paradigmas, por lo que las características "ocultas" que encontrará (aparte de los "comportamientos indefinidos" que debe evitar a toda costa) son usos inteligentes de las instalaciones.

La mayoría de esas instalaciones no son características integradas del lenguaje, sino que están basadas en bibliotecas.

El más importante es el RAII , a menudo ignorado durante años por los desarrolladores de C ++ que vienen del mundo C. La sobrecarga del operador es a menudo una característica mal entendida que permite tanto un comportamiento similar a una matriz (operador de subíndice), operaciones similares a punteros (punteros inteligentes) y operaciones similares a incorporadas (multiplicar matrices.

El uso de la excepción es a menudo difícil, pero con algo de trabajo, puede producir un código realmente robusto a través de especificaciones de seguridad de excepción (incluido el código que no fallará o que tendrá características similares a las de confirmación que tendrán éxito o volverán a su estado original).

La característica "oculta" más famosa de C ++ es la metaprogramación de plantillas , ya que le permite ejecutar su programa parcial (o totalmente) en tiempo de compilación en lugar de en tiempo de ejecución. Sin embargo, esto es difícil y debe tener un conocimiento sólido de las plantillas antes de probarlo.

Otros hacen uso del paradigma múltiple para producir "formas de programación" fuera del antepasado de C ++, es decir, C.

Mediante el uso de functores , puede simular funciones, con seguridad de tipo adicional y estado. Usando el patrón de comando , puede retrasar la ejecución del código. La mayoría de los otros patrones de diseño se pueden implementar fácil y eficientemente en C ++ para producir estilos de codificación alternativos que no se supone que estén dentro de la lista de "paradigmas oficiales de C ++".

Mediante el uso de plantillas , puede producir código que funcionará en la mayoría de los tipos, incluido el que pensó al principio. También puede aumentar la seguridad de los tipos (como un malloc / realloc / free seguro de tipos automatizado). Las características de los objetos de C ++ son realmente poderosas (y, por lo tanto, peligrosas si se usan sin cuidado), pero incluso el polimorfismo dinámico tiene su versión estática en C ++: el CRTP .

He descubierto que la mayoría de los libros del tipo " C ++ eficaz " de Scott Meyers o los libros del tipo " C ++ excepcional " de Herb Sutter son fáciles de leer y constituyen un tesoro de información sobre las características conocidas y menos conocidas de C ++.

Entre mis preferidos se encuentra uno que debería hacer que el cabello de cualquier programador de Java se levante del horror: en C ++, la forma más orientada a objetos de agregar una característica a un objeto es a través de una función no miembro no amiga, en lugar de un miembro- función (es decir, método de clase), porque:

  • En C ++, la interfaz de una clase son sus funciones miembro y las funciones no miembros en el mismo espacio de nombres

  • Las funciones no amigas no miembros no tienen acceso privilegiado a la clase interna. Como tal, el uso de una función miembro sobre una no miembro no amiga debilitará la encapsulación de la clase.

Esto nunca deja de sorprender incluso a los desarrolladores experimentados.

(Fuente: Entre otros, Gurú de la semana en línea de Herb Sutter # 84: http://www.gotw.ca/gotw/084.htm )

paercebal
fuente
+1 respuesta muy completa. está incompleto por razones obvias (de lo contrario, ¡ya no habría "características ocultas"!): p en el primer punto al final de la respuesta, mencionaste miembros de una interfaz de clase. ¿Quieres decir ".. son sus funciones miembro y las funciones no miembro de amigo "?
wilhelmtell
lo que está mencionando con 1 debe ser búsqueda de koenig, ¿verdad?
Özgür
1
@wilhelmtell: No, no, no ... :-p ... Quiero decir "sus funciones miembro y funciones no miembro NO AMIGAS" ... La búsqueda de Koenig se asegurará de que estas funciones se consideren antes que otras " funciones externas en su búsqueda de símbolos
paercebal
7
Gran publicación, y +1 especialmente para la última parte, que muy pocas personas se dan cuenta. Probablemente también agregaría la biblioteca Boost como una "característica oculta". Considero que es la biblioteca estándar que debería haber tenido C ++. ;)
jalf
118

Una característica del idioma que considero algo oculta, porque nunca había oído hablar de ella durante todo mi tiempo en la escuela, es el alias del espacio de nombres. No me llamó la atención hasta que encontré ejemplos en la documentación de boost. Por supuesto, ahora que lo sé, puede encontrarlo en cualquier referencia estándar de C ++.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
Jason Mock
fuente
1
Supongo que esto es útil si no quieres usar using.
Siqi Lin
4
También es útil como una forma de cambiar entre implementaciones, ya sea seleccionando, por ejemplo, seguro para subprocesos o no seguro para subprocesos, o la versión 1 frente a 2.
Tony Delroy
3
Es especialmente útil si está trabajando en un proyecto muy grande con grandes jerarquías de espacios de nombres y no desea que sus encabezados causen contaminación del espacio de nombres (y desea que sus declaraciones de variables sean legibles por humanos).
Brandon Bohrer
102

No solo se pueden declarar variables en la parte de inicio de un forbucle, sino también clases y funciones.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Eso permite múltiples variables de diferentes tipos.

Johannes Schaub - litb
fuente
31
Es bueno saber que puedes hacerlo, pero personalmente trataría de evitar hacer algo así. Sobre todo porque es difícil de leer.
Zoomulator
2
En realidad, lo que funcionaría en este contexto es usar un par: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz
2
@Valentin bueno, entonces te recomiendo que intentes hacer un informe de error contra VS2008 en lugar de rechazar la función oculta. Claramente es culpa de su compilador.
Johannes Schaub - litb
2
Hmm, tampoco funciona en msvc10. Qué triste :(
Avakar
2
@avakar de hecho, gcc introdujo un error que también lo hace rechazar, en la v4.6 :) ver gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb
77

El operador de matriz es asociativo.

A [8] es sinónimo de * (A + 8). Dado que la suma es asociativa, se puede reescribir como * (8 + A), que es sinónimo de ..... 8 [A]

No dijiste útil ... :-)

Colin Jensen
fuente
15
En realidad, al usar este truco, debes prestar atención al tipo que estás usando. A [8] es en realidad la octava A mientras que 8 [A] es el entero Ath que comienza en la dirección 8. Si A es un byte, tienes un error.
Vincent Robert
38
te refieres a "conmutativo" donde dices "asociativo"?
DarenW
28
Vincent, estás equivocado. El tipo de Ano importa en absoluto. Por ejemplo, si Afuera a char*, el código aún sería válido.
Konrad Rudolph
11
Tenga en cuenta que A debe ser un puntero y no un operador de sobrecarga de clase [].
David Rodríguez - dribeas
15
Vincent, en esto tiene que haber un tipo integral y un tipo de puntero, y ni a C ni a C ++ les importa cuál va primero.
David Thornley
73

Una cosa que se sabe poco es que las uniones también pueden ser plantillas:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

Y también pueden tener constructores y funciones miembro. Nada que tenga que ver con la herencia (incluidas las funciones virtuales).

Johannes Schaub - litb
fuente
¡Interesante! Entonces, ¿debes inicializar todos los miembros? ¿Sigue el orden de estructura habitual, lo que implica que el último miembro se inicializará "encima" de los miembros anteriores?
j_random_hacker
j_random_hacker oh, eso es una tontería. buena atrapada. Lo escribí como si fuera una estructura. espera, lo arreglaré
Johannes Schaub - litb
¿No invoca esto un comportamiento indefinido?
Greg Bacon
7
@gbacon, sí, invoca un comportamiento indefinido si Fromy Tose configuran y usan en consecuencia. Sin embargo, tal unión se puede usar con un comportamiento definido ( Tosiendo una matriz de caracteres sin firmar o una estructura que comparte una secuencia inicial con From). Incluso si lo usa de una manera indefinida, aún podría ser útil para trabajos de bajo nivel. De todos modos, este es solo un ejemplo de una plantilla de unión; puede haber otros usos para una unión con plantilla.
Johannes Schaub - litb
3
Cuidado con el constructor. Tenga en cuenta que solo debe construir el primer elemento, y solo está permitido en C ++ 0x. Según el estándar actual, debe ceñirse a tipos trivialmente construibles. Y sin destructores.
Potatoswatter
72

C ++ es un estándar, no debería haber características ocultas ...

C ++ es un lenguaje de múltiples paradigmas, puede apostar su último dinero a que haya funciones ocultas. Un ejemplo entre muchos: metaprogramación de plantillas . Nadie en el comité de estándares tenía la intención de que hubiera un sublenguaje completo de Turing que se ejecute en tiempo de compilación.

Konrad Rudolph
fuente
65

Otra característica oculta que no funciona en C es la funcionalidad del unario + operador . Puedes usarlo para promover y degradar todo tipo de cosas.

Convertir una enumeración en un número entero

+AnEnumeratorValue

Y su valor de enumerador que anteriormente tenía su tipo de enumeración ahora tiene el tipo de entero perfecto que puede ajustarse a su valor. ¡Manualmente, difícilmente conocerías a ese tipo! Esto es necesario, por ejemplo, cuando desea implementar un operador sobrecargado para su enumeración.

Obtener el valor de una variable

Tiene que usar una clase que usa un inicializador estático en su clase sin una definición fuera de clase, pero a veces no se puede vincular. El operador puede ayudar a crear un temporal sin hacer suposiciones o dependencias de su tipo.

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Decayar una matriz a un puntero

¿Quiere pasar dos punteros a una función, pero simplemente no funcionará? El operador puede ayudar

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}
Johannes Schaub - litb
fuente
61

La vida útil de los temporales vinculados a referencias constantes es algo que pocas personas conocen. O al menos es mi pieza favorita de conocimiento de C ++ que la mayoría de la gente no conoce.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope
MSN
fuente
3
¿Puedes elaborar? Como es, solo estás bromeando;)
Joseph Garvin
8
ScopeGuard ( ddj.com/cpp/184403758 ) es un gran ejemplo que aprovecha esta función.
MSN
2
Estoy con Joseph Garvin. Por favor, ilumínanos.
Peter Mortensen
Lo acabo de hacer en los comentarios. Además, es una consecuencia natural de usar un parámetro de referencia constante.
MSN
1
@Oak: stackoverflow.com/questions/256724/…
BlueRaja - Danny Pflughoeft
52

Una buena característica que no se usa con frecuencia es el bloque try-catch de toda la función:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

El uso principal sería traducir la excepción a otra clase de excepción y volver a lanzar, o traducir entre excepciones y manejo de código de error basado en retorno.

vividos
fuente
No creo que puedas returndesde el bloque de captura de Function Try, solo volver a lanzar.
Constantin
Intenté compilar lo anterior y no dio ninguna advertencia. Creo que el ejemplo anterior funciona.
vividos
7
El retorno solo está prohibido para constructores. El bloque try de la función de un constructor detectará errores al inicializar la base y los miembros (el único caso en el que un bloque try de la función hace algo diferente a simplemente probar dentro de la función); no volver a lanzar resultaría en un objeto incompleto.
puetzk
Si. Esto es muy útil. Escribí macros BEGIN_COM_METHOD y END_COM_METHOD para detectar excepciones y devolver HRESULTS para que las excepciones no se filtraran de una clase que implementa una interfaz COM. Funcionó bien.
Scott Langham
3
Como señaló @puetzk, esta es la única forma de manejar las excepciones lanzadas por cualquier cosa en la lista de inicializadores de un constructor , como los constructores de las clases base o los de los miembros de datos.
anton.burger
44

Muchos conocen la metafunción identity/ id, pero hay un buen caso de uso para los casos que no son de plantilla: Facilidad para escribir declaraciones:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

¡Ayuda enormemente a descifrar declaraciones de C ++!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };
Johannes Schaub - litb
fuente
Interesante, pero inicialmente tuve más problemas para leer algunas de esas definiciones. Otra forma de solucionar el problema de adentro hacia afuera con las declaraciones de C ++ es escribir algunos alias de tipo de plantilla: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);o pointer<void(int)> f(pointer<void()>);ofunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53
42

Una característica bastante oculta es que puede definir variables dentro de una condición if, y su alcance se extenderá solo sobre los bloques if y else:

if(int * p = getPointer()) {
    // do something
}

Algunas macros usan eso, por ejemplo, para proporcionar un alcance "bloqueado" como este:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

También BOOST_FOREACH lo usa debajo del capó. Para completar esto, no solo es posible en un if, sino también en un switch:

switch(int value = getIt()) {
    // ...
}

y en un bucle while:

while(SomeThing t = getSomeThing()) {
    // ...
}

(y también en una condición for). Pero no estoy muy seguro de si estos son tan útiles :)

Johannes Schaub - litb
fuente
¡Ordenado! Nunca supe que podría hacer eso ... habría ahorrado (y lo hará) algunos problemas al escribir código con valores de retorno de error. ¿Hay alguna forma de tener un condicional en lugar de solo! = 0 en esta forma? si ((int r = func ()) <0) no parece funcionar ...
puetzk
puetzk, no, no lo hay. pero me alegro de que te guste :)
Johannes Schaub - litb
4
@Frerich, esto no es posible en el código C en absoluto. Creo que estás pensando if((a = f()) == b) ..., pero esta respuesta en realidad declara una variable en la condición.
Johannes Schaub - litb
1
@Angry es muy diferente, porque la declaración de variable se prueba para su valor booleano de inmediato. También hay un mapeo para bucles for, que parece que for(...; int i = foo(); ) ...;This pasará por el cuerpo siempre que isea ​​verdadero, inicializándolo cada vez de nuevo. El ciclo que muestra es simplemente una demostración de una declaración de variable, pero no una declaración de variable que actúa simultáneamente como una condición :)
Johannes Schaub - litb
5
Muy bien, excepto que no mencionaste que el uso previsto para esta función era para lanzamientos de punteros dinámicos, creo.
mmocny
29

Evitar que el operador de coma llame a las sobrecargas del operador

A veces, hace un uso válido del operador de coma, pero desea asegurarse de que ningún operador de coma definido por el usuario se interponga, porque, por ejemplo, confía en los puntos de secuencia entre el lado izquierdo y el derecho o desea asegurarse de que nada interfiera con el acción. Aquí es donde void()entra en juego:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Ignore los marcadores de posición que puse para la condición y el código. Lo importante es el void(), que hace que el compilador fuerce el uso del operador de coma incorporado. Esto también puede ser útil al implementar clases de rasgos, a veces.

Johannes Schaub - litb
fuente
Solo usé esto para terminar con mi ignorante expresión exagerada . :)
GManNickG
28

Inicialización de matriz en constructor. Por ejemplo, en una clase si tenemos una matriz de intcomo:

class clName
{
  clName();
  int a[10];
};

Podemos inicializar todos los elementos de la matriz a su valor predeterminado (aquí todos los elementos de la matriz a cero) en el constructor como:

clName::clName() : a()
{
}
Sirish
fuente
6
Puede hacer esto con cualquier matriz en cualquier lugar.
Potatoswatter
@Potatoswatter: más difícil de lo que parece, debido al análisis más molesto. No puedo pensar en ningún otro lugar donde se pueda hacer, excepto tal vez un valor de retorno
Mooing Duck
Si el tipo de la matriz es un tipo de clase, entonces esto no es necesario, ¿verdad?
Thomas Eding
27

Oooh, puedo crear una lista de los que odian a las mascotas:

  • Los destructores deben ser virtuales si pretendes usar polimórficamente
  • A veces, los miembros se inicializan de forma predeterminada, a veces no
  • Las clases locales no se pueden usar como parámetros de plantilla (las hace menos útiles)
  • especificadores de excepciones: parecen útiles, pero no lo son
  • las sobrecargas de funciones ocultan funciones de clase base con diferentes firmas.
  • ninguna estandarización útil en la internacionalización (conjunto de caracteres ancho estándar portátil, ¿alguien? Tendremos que esperar hasta C ++ 0x)

En el lado positivo

  • característica oculta: función de bloques de prueba. Desafortunadamente no le he encontrado un uso. Sí, sé por qué lo agregaron, pero tienes que volver a lanzar un constructor que lo hace inútil.
  • Vale la pena mirar detenidamente las garantías de STL sobre la validez del iterador después de la modificación del contenedor, lo que puede permitirle hacer algunos bucles un poco más agradables.
  • Impulso: no es un secreto, pero vale la pena usarlo.
  • Optimización del valor de retorno (no es obvio, pero está específicamente permitido por el estándar)
  • Functors, también conocidos como objetos de función, también como operator (). Esto es utilizado ampliamente por el STL. no es realmente un secreto, pero es un efecto secundario ingenioso de la sobrecarga del operador y las plantillas.
Robert
fuente
16
odio a las mascotas: no hay ABI definida para las aplicaciones C ++, a diferencia de las C que todo el mundo usa porque todos los lenguajes pueden garantizar la llamada a una función C, nadie puede hacer lo mismo con C ++.
gbjbaanb
8
Los destructores deben ser virtuales solo si tiene la intención de destruir polimórficamente, lo cual es un poco sutilmente diferente del primer punto.
David Rodríguez - dribeas
2
Con C ++ 0x, los tipos locales se pueden usar como parámetros de plantilla.
tstenner
1
Con C ++ 0x, los destructores serán virtuales si el objeto tiene alguna función virtual (es decir, una vtable).
Macke
no olvide NRVO y, por supuesto, se permite cualquier optimización siempre que no cambie la salida del programa
jk.
26

Puede acceder a datos protegidos y miembros de funciones de cualquier clase, sin un comportamiento indefinido y con la semántica esperada. Siga leyendo para ver cómo. Lea también el informe de defectos sobre esto.

Normalmente, C ++ le prohíbe acceder a miembros protegidos no estáticos del objeto de una clase, incluso si esa clase es su clase base

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

Eso está prohibido: usted y el compilador no saben a qué apunta realmente la referencia. Podría ser un Cobjeto, en cuyo caso la clase Bno tiene nada que ver con sus datos. Dicho acceso solo se concede si xes una referencia a una clase derivada o una derivada de ella. Y podría permitir que un fragmento de código arbitrario lea cualquier miembro protegido simplemente creando una clase "desechable" que lea los miembros, por ejemplo std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Seguramente, como ve, esto causaría demasiado daño. Pero ahora, los indicadores de miembros permiten eludir esta protección. El punto clave es que el tipo de puntero de miembro está vinculado a la clase que realmente contiene dicho miembro, no a la clase que especificó al tomar la dirección. Esto nos permite eludir la comprobación

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

Y, por supuesto, también funciona con el std::stackejemplo.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Eso será aún más fácil con una declaración using en la clase derivada, que hace que el nombre del miembro sea público y se refiere al miembro de la clase base.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}
Johannes Schaub - litb
fuente
26

Otra característica oculta es que puede llamar a objetos de clase que se pueden convertir en punteros de función o referencias. La resolución de sobrecarga se realiza sobre el resultado de ellos y los argumentos se envían perfectamente.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Se denominan "funciones de llamada sustitutas".

Johannes Schaub - litb
fuente
1
Cuando dice que la resolución de sobrecarga se realiza en el resultado de ellos, ¿quiere decir que en realidad lo convierte a ambos Functors y luego sobrecarga la resolución? Intenté imprimir algo en el operador Func1 * () y el operador Func2 * (), pero parece elegir el correcto cuando determina qué operador de conversión invocar.
navegador
3
@navigator, sí, conceptualmente se convierte en ambos y luego elige el mejor. No necesita llamarlos realmente, porque sabe por el tipo de resultado lo que ya producirán. La llamada real se realiza cuando resulta lo que finalmente se eligió.
Johannes Schaub - litb
26

Funciones ocultas:

  1. Las funciones virtuales puras pueden tener implementación. Ejemplo común, destructor virtual puro.
  2. Si una función lanza una excepción que no figura en sus especificaciones de excepción, pero la función tiene std::bad_exceptionen su especificación de excepción, la excepción se convierte std::bad_exceptiony se lanza automáticamente. De esa manera, al menos sabrá que bad_exceptionse lanzó un. Leer más aquí .

  3. función prueba bloques

  4. La palabra clave de plantilla para eliminar la ambigüedad de typedefs en una plantilla de clase. Si el nombre de un miembro de especialización de plantilla aparece después de una ., ->o ::del operador, y que el nombre tiene parámetros de plantilla expresamente calificados, el prefijo del nombre de la plantilla miembro de la plantilla de palabras clave. Leer más aquí .

  5. Los valores predeterminados de los parámetros de función se pueden cambiar en tiempo de ejecución. Leer más aquí .

  6. A[i] funciona tan bien como i[A]

  7. Las instancias temporales de una clase se pueden modificar. Se puede invocar una función miembro no constante en un objeto temporal. Por ejemplo:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Leer más aquí .

  8. Si dos tipos diferentes están presentes antes y después de la expresión del operador :ternary ( ?:), entonces el tipo resultante de la expresión es el más general de los dos. Por ejemplo:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }
Sumant
fuente
P Papá: A [i] == * (A + i) == * (i + A) == i [A]
abelenky
Recibo la conmutación, es solo que esto significa que [] no tiene valor semántico propio y es simplemente equivalente a un reemplazo de estilo macro donde "x [y]" se reemplaza con "(* ((x) + (y ))) ". No es en absoluto lo que esperaba. Me pregunto por qué se define de esta manera.
P Daddy
Retrocompatibilidad con C
jmucchiello
2
Con respecto a su primer punto: hay un caso particular en el que debe implementar una función virtual pura: destructores virtuales puros.
Frerich Raabe
24

map::operator[]crea una entrada si falta la clave y devuelve una referencia al valor de entrada construido por defecto. Entonces puedes escribir:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Me sorprende la cantidad de programadores de C ++ que no saben esto.

Constantin
fuente
11
Y en el extremo opuesto no se puede usar el operador [] en un mapa constante
David Rodríguez - dribeas
2
+1 para Nick, la gente puede volverse loca si no lo saben .find().
LiraNuna
o " const map::operator[]genera mensajes de error"
solo alguien
2
No es una característica del idioma, es una característica de la biblioteca de plantillas estándar. También es bastante obvio, ya que el operador [] devuelve una referencia válida.
Ramon Zarazua B.
2
Tuve que usar mapas en C # por un tiempo, donde los mapas no se comportan de esa manera, para darme cuenta de que esta es una característica. Pensé que me molestaba más de lo que lo usaba, pero parece que estaba equivocado. Me lo pierdo en C #.
sbi
20

Poner funciones o variables en un espacio de nombres sin nombre desaprueba el uso de staticpara restringirlas al alcance del archivo.

Jim Hunziker
fuente
"desaprueba" es un término fuerte ...
Potatoswatter
@Potato: comentario anterior, lo sé, pero el estándar dice que el uso de estática en el ámbito del espacio de nombres está en desuso, con preferencia por los espacios de nombres sin nombre.
GManNickG
@GMan: no hay problema, no creo que las páginas de SO realmente "mueran". Solo para ambos lados de la historia, staticel alcance global no está desaprobado de ninguna manera. (Para referencia: C ++ 03 §D.2)
Potatoswatter
Ah, en una lectura más cercana, "Un nombre declarado en el espacio de nombres global tiene un alcance de espacio de nombres global (también llamado alcance global)". ¿Eso realmente significa eso?
Potatoswatter
@Potato: Sí. :) staticuse solo debe usarse dentro de un tipo de clase o función.
GManNickG
19

La definición de funciones de amigos ordinarias en las plantillas de clase necesita una atención especial:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

En este ejemplo, dos instancias diferentes crean dos definiciones idénticas: una violación directa de la ODR

Por lo tanto, debemos asegurarnos de que los parámetros de plantilla de la plantilla de clase aparezcan en el tipo de cualquier función amiga definida en esa plantilla (a menos que queramos evitar más de una instanciación de una plantilla de clase en un archivo en particular, pero esto es bastante improbable). Apliquemos esto a una variación de nuestro ejemplo anterior:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Descargo de responsabilidad: he pegado esta sección de C ++ Templates: The Complete Guide / Section 8.4

Özgür
fuente
18

las funciones nulas pueden devolver valores nulos

Poco conocido, pero el siguiente código está bien

void f() { }
void g() { return f(); }

Así como el siguiente de aspecto extraño

void f() { return (void)"i'm discarded"; }

Sabiendo esto, puede aprovechar en algunas áreas. Un ejemplo: las voidfunciones no pueden devolver un valor, pero tampoco puede devolver nada, porque se pueden crear instancias con non-void. En lugar de almacenar el valor en una variable local, lo que provocará un error void, simplemente devuelva un valor directamente

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};
Johannes Schaub - litb
fuente
17

Leer un archivo en un vector de cadenas:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator

Jason Baker
fuente
8
O: vector <cadena> V ((istream_iterator <cadena> (cin)), istream_iterator <cadena>);
UncleBens
5
quiere decir vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- paréntesis faltantes después del segundo
parámetro
1
Esta no es realmente una característica oculta de C ++. Más de una función STL. STL! = A language
Nick Bedford
14

Puede crear plantillas de campos de bits.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Todavía no se me ha ocurrido ningún propósito para esto, pero seguro que me sorprendió.

Dragón Kaz
fuente
1
Vea aquí, donde lo sugerí recientemente para la aritmética de n bits: stackoverflow.com/questions/8309538/…
vea
14

Una de las gramáticas más interesantes de todos los lenguajes de programación.

Tres de estas cosas van juntas, y dos son algo completamente diferente ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Todos menos el tercero y el quinto definen un SomeTypeobjeto en la pila y lo inicializan (con uen los dos primeros casos y el constructor predeterminado en el cuarto. El tercero declara una función que no toma parámetros y devuelve a SomeType. El quinto declara de manera similar una función que toma un parámetro por valor de tipo SomeTypenombrado u.

Eclipse
fuente
¿Hay alguna diferencia entre 1º y 2º? sin embargo, sé que ambas son inicializaciones.
Özgür
Contraloría: No lo creo. Ambos terminarán llamando al constructor de copias, aunque el primero PARECE como el operador de asignación, en realidad es el constructor de copias.
abelenky
1
Si u es un tipo diferente de SomeType, entonces el primero llamará primero al constructor de conversión y luego al constructor de copia, mientras que el segundo solo llamará al constructor de conversión.
Eclipse
3
El primero es una llamada implícita del constructor, el segundo es una llamada explícita. Mire el siguiente código para ver la diferencia: #include <iostream> class sss {public: explícito sss (int) {std :: cout << "int" << std :: endl; }; sss (doble) {std :: cout << "doble" << std :: endl; }; }; int main () {sss ddd (7); // llama al constructor int sss xxx = 7; // llama al constructor doble return 0; }
Kirill V. Lyadvinsky
Verdadero: la primera línea no funcionará si el constructor se declara explícito.
Eclipse
12

Deshacerse de las declaraciones futuras:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Escribir sentencias de cambio con operadores?::

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Haciendo todo en una sola línea:

void a();
int b();
float c = (a(),b(),1.0f);

Poniendo a cero estructuras sin memset:

FStruct s = {0};

Normalizar / envolver valores de ángulo y tiempo:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Asignar referencias:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
AareP
fuente
2
FStruct s = {};es aún más corto.
Constantin
En el último ejemplo, sería más sencillo con: a (); si(); flotar c = 1.0f;
Zifre
2
Esta sintaxis "float c = (a (), b (), 1.0f);" es útil para acentuar la operación de asignación (asignación de "c"). Las operaciones de asignación son importantes en la programación porque es menos probable que se conviertan en obsoletas en la OMI. No sé por qué, podría tener algo que ver con la programación funcional donde el estado del programa se reasigna a cada cuadro. PD. Y no, "int d = (11,22,1.0f)" será igual a "1". Probado hace un minuto con VS2008.
AareP
2
+1 ¿No deberías llamar main ? Sugeriría global().main();y simplemente olvídese del singleton ( puede trabajar con el temporal, lo que prolonga su vida útil )
vea el
1
Dudo que asignar referencias sea portátil. Sin embargo, me encanta la estructura para renunciar a las declaraciones futuras.
Thomas Eding
12

El operador condicional ternario ?:requiere que su segundo y tercer operando tengan tipos "agradables" (hablando informalmente). Pero este requisito tiene una excepción (juego de palabras): el segundo o tercer operando puede ser una expresión de lanzamiento (que tiene el tipovoid ), independientemente del tipo del otro operando.

En otras palabras, uno puede escribir las siguientes expresiones C ++ perfectamente válidas usando el ?:operador

i = a > b ? a : throw something();

Por cierto, el hecho de que throw expression sea en realidad una expresión (de tipo void) y no una declaración es otra característica poco conocida del lenguaje C ++. Esto significa, entre otras cosas, que el siguiente código es perfectamente válido

void foo()
{
  return throw something();
}

aunque no tiene mucho sentido hacerlo de esta manera (tal vez en algún código de plantilla genérico esto pueda ser útil).

Hormiga
fuente
Por lo que vale, Neil tiene una pregunta sobre esto: stackoverflow.com/questions/1212978/… , solo para obtener información adicional.
GManNickG
12

La regla de dominancia es útil, pero poco conocida. Dice que incluso si se encuentra en una ruta no única a través de una red de clase base, la búsqueda de nombre para un miembro parcialmente oculto es única si el miembro pertenece a una clase base virtual:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

He utilizado esto para implementar el soporte de alineación que determina automáticamente la alineación más estricta mediante la regla de dominio.

Esto no solo se aplica a las funciones virtuales, sino también a los nombres typedef, miembros estáticos / no virtuales y cualquier otra cosa. Lo he visto utilizado para implementar rasgos sobrescribibles en metaprogramas.

Johannes Schaub - litb
fuente
1
Ordenado. ¿Alguna razón en particular que incluyó struct Cen su ejemplo ...? Salud.
Tony Delroy