¿No te gusta C++ cuando se trata de la línea de preguntas "características ocultas de"? Pensé que lo tiraría por ahí. ¿Cuáles son algunas de las características ocultas de C++?
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 utilizar como un lvalor:
(a == 0 ? a : b) = 1;
que es una abreviatura de
if (a == 0)
a = 1;
else
b = 1;
Usar con precaución :-)
Puede poner URI en la fuente de C++ sin error. Por ejemplo:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
Aritmética de punteros.
Los programadores de C++ prefieren evitar los punteros debido a los errores que pueden introducirse.
¿El C++ más genial que he visto? Los literales analógicos.
Estoy de acuerdo con la mayoría de las publicaciones: C++ es un lenguaje de paradigma múltiple, por lo que las funciones "ocultas" que encontrará (además de "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 basadas en bibliotecas.
El más importante es el RAII, a menudo ignorado durante años por los desarrolladores de C++ procedentes del mundo C. La sobrecarga del operador es a menudo una característica mal entendida que habilita tanto el comportamiento de tipo de arreglo (operador de subíndice), las operaciones de tipo puntero (punteros inteligentes) como las operaciones de tipo incorporada (matrices de multiplicación).
El uso de exception a menudo es difícil, pero con un poco de trabajo, puede producir un código realmente robusto a través de las especificaciones de exception safety (incluido el código que no fallará, o que tendrá características de compromiso que son que tendrá éxito, o volverá a su estado original).
La característica "oculta" más famosa de C++ es template metaprogramming, ya que le permite ejecutar su programa parcialmente (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 sólido conocimiento de las plantillas antes de intentarlo.
Otros hacen uso del paradigma múltiple para producir "formas de programación" fuera del ancestro de C++, es decir, C.
Al usar functors, puede simular funciones, con la seguridad de tipo adicional y con estado. Usando el patrón command, puede retrasar la ejecución del código. La mayoría de los otros patrones de diseño pueden implementarse 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++".
Al usar templates, puede producir un código que funcionará en la mayoría de los tipos, incluido el que no pensó al principio. También puede aumentar la seguridad de los tipos (como un malloc/realloc/free 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.
Descubrí que la mayoría de los libros de tipo "Efectivo C++" de Scott Meyers o los libros de tipo "Excepcional C++" de Herb Sutter son tanto fáciles de leer como de tesoros de información. sobre características conocidas y menos conocidas de C++.
Uno de mis favoritos es 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 para agregar una característica a un objeto es a través de una función que no sea miembro y no sea un amigo, en lugar de miembro-función (es decir, método de clase), porque:
En C++, una interfaz de clase es tanto sus funciones miembro como las funciones no miembros en el mismo espacio de nombres.
las funciones que no son de amigos que no son miembros no tienen acceso privilegiado a la clase interna. Como tal, el uso de una función miembro sobre un miembro que no sea miembro no amigo debilitará la encapsulación de la clase.
Esto nunca deja de sorprender incluso a los desarrolladores experimentados.
(Fuente: Entre otros, el Gurú de la Semana # 84 de Herb Sutter: http://www.gotw.ca/gotw/084.htm )
Una característica del idioma que considero un tanto oculta, porque nunca había oído hablar de ella durante todo mi tiempo en la escuela, es el alias de espacio de nombres. No me llamó la atención hasta que encontré ejemplos de ello en la documentación de impulso. Por supuesto, ahora que lo conozco, puede encontrarlo en cualquier referencia estándar de C++.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
Las variables no solo se pueden declarar en la parte de inicio de un bucle for
, sino también las 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 un sinónimo de * (A + 8). Como la suma es asociativa, se puede reescribir como * (8 + A), que es un sinónimo de ..... 8 [A]
No dijiste útil ... :-)
Una cosa que se sabe poco es que los sindicatos 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 pueden tener constructores y funciones miembro también. Simplemente nada que tenga que ver con la herencia (incluidas las funciones virtuales).
C++ es un estándar, no debería haber ninguna característica oculta ...
C++ es un lenguaje de paradigma múltiple, puede apostar su último dinero a que haya características ocultas. Un ejemplo de muchos: metaprogramación de plantillas . Nadie en el comité de estándares pretendía 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 operador único de +
. Puedes usarlo para promocionar y descomponer todo tipo de cosas.
+AnEnumeratorValue
Y el valor de su enumerador que anteriormente tenía su tipo de enumeración ahora tiene el tipo entero perfecto que puede ajustarse a su valor. Manualmente, difícilmente sabrías ese tipo! Esto es necesario, por ejemplo, cuando desea implementar un operador sobrecargado para su enumeración.
¿Debe usar una clase que use un inicializador estático dentro de la clase sin una definición fuera de clase, pero a veces no se puede vincular? El operador puede ayudar a crear un temporal sin realizar supuestos o dependencias en 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);
}
¿Quieres pasar dos punteros a una función, pero simplemente no funciona? 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 de los temporales vinculados a las constantes referencias es una que pocas personas conocen. O al menos es mi conocimiento favorito 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 característica de Niza que no se usa a menudo 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 realizar, o traducir entre excepciones y el manejo del código de error basado en el retorno.
Muchos conocen la meta función identity
/id
, pero existe un buen caso de uso para los casos sin plantillas:
// 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 a descifrar las declaraciones de C++ en gran medida!
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
Una característica bastante oculta es que puede definir variables dentro de una condición if, y su alcance abarcará solo los bloques if y sus 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 utiliza bajo el 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 de tiempo:
while(SomeThing t = getSomeThing()) {
// ...
}
(y también en una condición). Pero no estoy muy seguro de si estos son tan útiles :)
Algunas veces hace un uso válido del operador de coma, pero quiere asegurarse de que ningún operador de coma definido por el usuario se interponga en el camino, porque, por ejemplo, depende de puntos de secuencia entre el lado izquierdo y derecho o quiere asegurarse de que nada interfiere 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 la void()
, que hace que el compilador tenga la fuerza de usar el operador de coma incorporado. Esto puede ser útil al implementar clases de rasgos, a veces, también.
Inicialización de arrays 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 odios a las mascotas en su lugar:
En el lado positivo
Puede acceder a datos protegidos y miembros de funciones de cualquier clase, sin comportamiento indefinido, y con semántica esperada. Sigue 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é se refiere realmente la referencia. Podría ser un objeto C
, en cuyo caso la clase B
no tiene negocio ni pista sobre sus datos. Dicho acceso solo se otorga 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 a cualquier miembro protegido simplemente inventando una clase "desechable" que lea a 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 ves, esto causaría demasiado daño. ¡Pero ahora, los punteros de los miembros permiten eludir esta protección! El punto clave es que el tipo de puntero de un 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 evadir 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 ejemplo de 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) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Esto va a ser aún más fácil con una declaración de uso 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);
}
Características 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 en std::bad_exception
y se lanza automáticamente. De esa manera, al menos sabrás que se lanzó un bad_exception
. Leer más aquí .
bloques de prueba de funciones
La palabra clave de plantilla desambigua typedefs en una plantilla de clase. Si el nombre de una especialización de plantilla de miembro aparece después de un .
, ->
, o ::
operador, y ese nombre tiene parámetros de plantilla calificados explícitamente, prefije el nombre de la plantilla de miembro con la plantilla de palabra clave. Leer más aquí .
los parámetros predeterminados de la función se pueden cambiar en tiempo de ejecución. Leer más aquí .
A[i]
funciona tan bien como i[A]
Se pueden modificar instancias temporales de una clase! Una función miembro no constante puede invocarse 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 hay dos tipos diferentes antes y después de :
en la expresión del operador ternario (?:
), 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)
}
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 en 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
}
Estas se denominan "funciones de llamada sustitutas".
map::operator[]
crea una entrada si falta una clave y devuelve la referencia al valor de entrada construido por defecto. Así que 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;
Estoy sorprendido de cuántos programadores de C++ no saben esto.
Poner funciones o variables en un espacio de nombres sin nombre desaprueba el uso de static
para restringirlas al alcance del archivo.
La definición de funciones de amigo ordinarias en plantillas de clase requiere 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 ODR
Por lo tanto, debemos asegurarnos de que los parámetros de plantilla de la plantilla de clase aparezcan en el tipo de función amiga definida en esa plantilla (a menos que queramos evitar más de una creación de instancias de una plantilla de clase en un archivo 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 Plantillas de C++: La Guía completa / Sección 8.4
Poco conocido, pero el siguiente código está bien.
void f() { }
void g() { return f(); }
También como el siguiente de aspecto extraño
void f() { return (void)"i'm discarded"; }
Sabiendo esto, puedes aprovecharte en algunas áreas. Un ejemplo: las funciones void
no pueden devolver un valor, pero tampoco puede devolver solo nada, ya que se pueden crear instancias con no nulos. En lugar de almacenar el valor en una variable local, lo que causará un error para 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; !
};
Lee un archivo en un vector de cadenas:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
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 objeto SomeType
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 SomeType
. está declarando de manera similar una función que toma un parámetro por valor de tipo SomeType
llamado u
.
Puede plantilla de campos de bits.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Todavía no he encontrado ningún propósito para esto, pero seguro que me sorprendió.
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 nombres 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 usado esto para implementar alineación-soporte que determina automáticamente la alineación más estricta por medio de la regla de dominancia.
Esto no solo se aplica a las funciones virtuales, sino también a los nombres typedef, los miembros estáticos/no virtuales y cualquier otra cosa. Lo he visto usado para implementar rasgos sobrescribibles en los metaprogramas.
El operador condicional ternario ?:
requiere que su segundo y tercer operando tengan tipos "aceptables" (hablando de manera informal). 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 tipo void
), independientemente del tipo del otro operando.
En otras palabras, uno puede escribir las siguientes expresiones de C++ perfectamente correctas utilizando el operador ?:
i = a > b ? a : throw something();
Por cierto, el hecho de que lanzar expresión es 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 (quizás en algún código genérico de plantilla esto pueda ser útil).
Deshacerse de las declaraciones a plazo:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
Escribiendo instrucciones 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);
Cero estructuras sin memset:
FStruct s = {0};
Normalización/ajuste de los valores de ángulo y tiempo:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
Asignando referencias:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
Encontré este blog como un recurso asombroso sobre los arcanes de C++: C++ Truths .
Un secreto peligroso es
Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();
Mi secreto favorito que raramente veo usado:
class A
{
};
struct B
{
A a;
operator A&() { return a; }
};
void func(A a) { }
int main()
{
A a, c;
B b;
a=c;
func(b); //yeah baby
a=b; //gotta love this
}
Las clases locales son impresionantes:
struct MyAwesomeAbstractClass
{ ... };
template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
struct ans : MyAwesomeAbstractClass
{
// Make the implementation depend on T
};
return new ans(...);
}
bastante limpio, ya que no contamina el espacio de nombres con definiciones de clase inútiles ...
Una característica oculta, incluso oculta para los desarrolladores de GCC , es inicializar un miembro de la matriz utilizando una cadena literal. Supongamos que tiene una estructura que necesita trabajar con una matriz C y desea inicializar el miembro de la matriz con un contenido predeterminado
struct Person {
char name[255];
Person():name("???") { }
};
Esto funciona, y solo funciona con matrices de caracteres y inicializadores literales de cadena. No se necesita strcpy
!
Un ejemplo de muchos: metaprogramación de plantillas. Nadie en el comité de estándares pretendía que hubiera un sublenguaje completo de Turing que se ejecute en tiempo de compilación.
La metaprogramación de plantillas no es una característica oculta. Es incluso en la biblioteca de impulso. Ver MPL . Pero si "casi oculto" es lo suficientemente bueno, entonces eche un vistazo a las bibliotecas boost . Contiene muchas golosinas que no son de fácil acceso sin el respaldo de una biblioteca sólida.
Un ejemplo es boost.lambda library, que es interesante ya que C++ no tiene funciones lambda en el estándar actual.
Otro ejemplo es Loki , que "hace un uso extensivo de la metaprogramación de plantillas C++ e implementa varias herramientas de uso común: lista de tipos, functor, singleton, puntero inteligente, fábrica de objetos, visitante y métodos múltiples". [ Wikipedia ]
No hay funciones ocultas, pero el lenguaje C++ es muy poderoso y, con frecuencia, incluso los desarrolladores de estándares no pueden imaginar para qué se puede usar C++.
En realidad, a partir de una construcción de lenguaje lo suficientemente simple, puedes escribir algo muy poderoso. Muchas de estas cosas están disponibles en www.boost.org como ejemplos (y http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html entre ellos).
Para comprender la forma en que se puede combinar la construcción de un lenguaje simple con algo poderoso, es bueno leer "Plantillas de C++: La guía completa" por David Vandevoorde, Nicolai M. Josuttis y realmente libro mágico "Diseño moderno de C++. .. "por Andrei Alexandrescu .
Y finalmente, es difícil aprender C++, deberías intentar rellenarlo;)
Me parece que solo unas pocas personas conocen los espacios de nombres sin nombre:
namespace {
// Classes, functions, and objects here.
}
Los espacios de nombres sin nombre se comportan como si fueran reemplazados por:
namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
// original namespace body
}
"... donde todas las apariciones de [este nombre único] en una unidad de traducción son reemplazadas por el mismo identificador y este identificador difiere de todos los otros identificadores en todo el programa". [C++ 03, 7.3.1.1/1]
No estoy seguro de que esté oculto, pero hay algunos interesantes'trucos' que probablemente no sean obvios simplemente leyendo la especificación.
Hay un montón de "comportamiento indefinido". Puedes aprender cómo evitar que lean buenos libros y que lean los estándares.
La mayoría de los desarrolladores de C++ ignoran el poder de la metaprogramación de plantillas. Echa un vistazo a Loki Libary . Implementa varias herramientas avanzadas como lista de tipos, functor, singleton, puntero inteligente, fábrica de objetos, visitante y multimetods usando metaprogramación de plantillas extensivamente (desde wikipedia ) En la mayoría de los casos, podría considerarlos como una característica de c ++ "oculta".
De C++ Verdades .
Definir funciones que tienen firmas idénticas en el mismo ámbito, por lo que esto es legal:
template<class T> // (a) a base template
void f(T) {
std::cout << "f(T)\n";
}
template<>
void f<>(int*) { // (b) an explicit specialization
std::cout << "f(int *) specilization\n";
}
template<class T> // (c) another, overloads (a)
void f(T*) {
std::cout << "f(T *)\n";
}
template<>
void f<>(int*) { // (d) another identical explicit specialization
std::cout << "f(int *) another specilization\n";
}
main () no necesita un valor de retorno:
int main(){}
es el programa de C++ válido más corto.
Preste atención a la diferencia entre el puntero de función libre y las inicializaciones de puntero de función miembro:
función miembro:
struct S
{
void func(){};
};
int main(){
void (S::*pmf)()=&S::func;// & is mandatory
}
y función libre:
void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well;
}
Gracias a este redundante &, puede agregar manipuladores de flujo, que son funciones libres, en cadena sin él:
cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
map::insert(std::pair(key, value));
no se sobrescribe si el valor clave ya existe.
Puede crear una instancia de una clase justo después de su definición: (podría agregar que esta característica me ha dado cientos de errores de compilación debido a la falta de punto y coma, y nunca he visto a nadie usar esto en las clases)
class MyClass {public: /* code */} myClass;
Hay toneladas de construcciones "difíciles" en C++. Van desde implementaciones "simples" de clases selladas/finales usando herencia virtual. Y llegue a las construcciones de programación meta bastante "complejas" como Boost's MPL ( tutorial ). Las posibilidades de dispararse en el pie son infinitas, pero si se mantienen bajo control (es decir, programadores experimentados), brindan la mejor flexibilidad en términos de mantenibilidad y rendimiento.
Las claves de clase class y struct son casi idénticas. La principal diferencia es que las clases tienen acceso predeterminado para el acceso privado para miembros y bases, mientras que las estructuras son predeterminadas como públicas:
// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };
// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };
// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B
Los sindicatos también pueden tener miembros y métodos, y predeterminar el acceso público de manera similar a las estructuras.
Idioma de conversión indirecta :
Supongamos que estás diseñando una clase de puntero inteligente. Además de sobrecargar los operadores * y ->, una clase de puntero inteligente generalmente define un operador de conversión a bool:
template <class T>
class Ptr
{
public:
operator bool() const
{
return (rawptr ? true: false);
}
//..more stuff
private:
T * rawptr;
};
La conversión a bool permite a los clientes usar punteros inteligentes en expresiones que requieren operandos bool:
Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
cout<<"int value is: "<<*ptr <<endl;
else
cout<<"empty"<<endl;
Además, la conversión implícita a bool se requiere en declaraciones condicionales como:
if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
//we get here only of px isn't empty
}
Por desgracia, esta conversión automática abre la puerta a sorpresas desagradables:
Ptr <int> p1;
Ptr <double> p2;
//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl;
//prints 0, 1, or 2, although there isn't an overloaded operator+()
Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated
//surprise #2
if(pf==pq) //compares bool values, not pointers!
Solución: use el lenguaje de "conversión indirecta", mediante una conversión de puntero a miembro de datos [pMember] a bool, de modo que solo haya 1 conversión implícita, lo que evitará el comportamiento inesperado mencionado anteriormente: pMember-> bool en lugar de bool-> algo más.
Si el operador delete () toma un argumento de tamaño además de * void, eso significa que, altamente, será una clase base. Ese argumento de tamaño hace posible verificar el tamaño de los tipos para destruir el correcto. Aquí lo que Stephen Dewhurst dice acerca de esto:
Tenga en cuenta también que hemos empleado una versión de dos argumentos de la eliminación del operador en lugar de la versión de un argumento habitual. Esta versión de dos argumentos es otra versión "usual" de la eliminación de operador miembro que a menudo emplean las clases base que esperan que las clases derivadas hereden su implementación de eliminación de operador. El segundo argumento contendrá el tamaño del objeto que se está eliminando, información que a menudo es útil para implementar la administración de memoria personalizada.
Encuentro las instacciones de plantillas recursivas bastante bien:
template<class int>
class foo;
template
class foo<0> {
int* get<0>() { return array; }
int* array;
};
template<class int>
class foo<i> : public foo<i-1> {
int* get<i>() { return array + 1; }
};
Lo he usado para generar una clase con 10-15 funciones que devuelven punteros a varias partes de una matriz, ya que una API que usé requería un puntero de función para cada valor.
Es decir. Programando el compilador para generar un montón de funciones, a través de la recursión. Muy fácil. :)
Mi favorito (por el momento) es la falta de semáticas en una declaración como A = B = C. Lo que el valor de A es básicamente indeterminado.
Piensa en esto:
class clC
{
public:
clC& operator=(const clC& other)
{
//do some assignment stuff
return copy(other);
}
virtual clC& copy(const clC& other);
}
class clB : public clC
{
public:
clB() : m_copy()
{
}
clC& copy(const clC& other)
{
return m_copy;
}
private:
class clInnerB : public clC
{
}
clInnerB m_copy;
}
ahora A podría ser de un tipo inaccesible para cualquier otro que no sean objetos de tipo clB y tener un valor que no esté relacionado con C.
Agregar restricciones a las plantillas.
Indicadores de miembros y operador de indicadores de miembros -> *
#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
A* a = new A();
a->d = 8;
printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
return 0;
}
Para los métodos (a -> * & A :: e) () es un poco como Function.call () de javascript
var f = A.e
f.call(a)
Para los miembros es un poco como acceder con el operador []
a['d']
Puede ver todas las macros predefinidas a través de los modificadores de la línea de comandos con algunos compiladores. Esto funciona con gcc e icc (compilador C++ de Intel):
$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt
Para MSVC se enumeran en un lugar único . También podrían documentarse en un solo lugar para los demás, pero con los comandos anteriores puede claramente ver lo que está y no está definido y exactamente qué valores se utilizan, después de aplicar todos los otros comandos interruptores de línea.
Comparar (después de clasificar):
$ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt
class Empty {};
namespace std {
// #1 specializing from std namespace is okay under certain circumstances
template<>
void swap<Empty>(Empty&, Empty&) {}
}
/* #2 The following function has no arguments.
There is no 'unknown argument list' as we do
in C.
*/
void my_function() {
cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
// and this doesn't affect things outside of that scope
}
int main() {
using namespace std; /* #4 you can use using in function scopes */
cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
/* #6 falling off of main without an explicit return means "return 0;" */
}