¿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 ++?
¿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 ++?
Respuestas:
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 :-)
(value ? function1 : function2)()
.
function1
y function2
se convierten implícitamente en punteros de función, y el resultado se convierte implícitamente de nuevo.
Puede poner URI en la fuente C ++ sin errores. Por ejemplo:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
, que C ++ sí tiene). Todo lo que siga a dos barras es un comentario. Por lo tanto, con http://stackoverflow.com
, http
es una etiqueta (en teoría, podría escribir goto http;
) y //stackoverflow.com
es 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.
goto http;
, en realidad no sigue la URL. :(
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.
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 )
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 );
using
.
No solo se pueden declarar variables en la parte de inicio de un for
bucle, sino también clases y funciones.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Eso permite múltiples variables de diferentes tipos.
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 ... :-)
A
no importa en absoluto. Por ejemplo, si A
fuera a char*
, el código aún sería válido.
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).
From
y To
se configuran y usan en consecuencia. Sin embargo, tal unión se puede usar con un comportamiento definido ( To
siendo 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.
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.
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.
+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.
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);
}
¿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
}
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
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.
return
desde el bloque de captura de Function Try, solo volver a lanzar.
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; };
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;
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 :)
if((a = f()) == b) ...
, pero esta respuesta en realidad declara una variable en la condición.
for(...; int i = foo(); ) ...;
This pasará por el cuerpo siempre que i
sea 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 :)
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.
Inicialización de matriz en constructor. Por ejemplo, en una clase si tenemos una matriz de int
como:
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()
{
}
Oooh, puedo crear una lista de los que odian a las mascotas:
En el lado positivo
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 C
objeto, en cuyo caso la clase B
no tiene nada que ver con sus datos. Dicho acceso solo se concede si x
es 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::stack
ejemplo.
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);
}
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".
Funciones ocultas:
Si una función lanza una excepción que no figura en sus especificaciones de excepción, pero la función tiene std::bad_exception
en su especificación de excepción, la excepción se convierte std::bad_exception
y se lanza automáticamente. De esa manera, al menos sabrá que bad_exception
se lanzó un. Leer más aquí .
función prueba bloques
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í .
Los valores predeterminados de los parámetros de función se pueden cambiar en tiempo de ejecución. Leer más aquí .
A[i]
funciona tan bien como i[A]
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í .
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)
}
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.
.find()
.
const map::operator[]
genera mensajes de error"
Poner funciones o variables en un espacio de nombres sin nombre desaprueba el uso de static
para restringirlas al alcance del archivo.
static
el alcance global no está desaprobado de ninguna manera. (Para referencia: C ++ 03 §D.2)
static
use solo debe usarse dentro de un tipo de clase o función.
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
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 void
funciones 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; !
};
Leer un archivo en un vector de cadenas:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- paréntesis faltantes después del segundo
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ó.
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 SomeType
objeto en la pila y lo inicializan (con u
en 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 SomeType
nombrado u
.
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;
FStruct s = {};
es aún más corto.
main
? Sugeriría global().main();
y simplemente olvídese del singleton ( puede trabajar con el temporal, lo que prolonga su vida útil )
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).
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.
struct C
en su ejemplo ...? Salud.