2010-01-26 12 views
10

Me tomó mucho tiempo para darse cuenta de lo importante y sutiles que tienen variables que:C++ patrones específicos debido a diseño de lenguajes

1) existen en la pila

2) han llamado a sus destructores cuando se caen del alcance

son.

Estas dos cosas permiten cosas como:

A) RAII

B) refcounted GC

lo suficientemente interesante, (1) & (2) no están disponibles en los idiomas "inferiores" como C /Montaje; ni en idiomas "superiores" como Ruby/Python/Java (ya que el GC previene la destrucción predecible de los objetos).

Tengo curiosidad por saber qué otras técnicas conoces que son muy específicas de C++, debido a las opciones de diseño del lenguaje.

Gracias!

Editar: Si su respuesta es "esto funciona en C++ & este otro idioma", está bien también. Las cosas que quiero aprender son similares a:

Al elegir no tener ciertas características (como GC), obtenemos otras características (como RAII + destrucción predecible de objetos). En qué áreas de C++, al elegir NOT tienen características que otros lenguajes de "nivel superior" tienen, C++ logra patrones que esos lenguajes de nivel superior no pueden expresar.

+0

no creo que C++ tiene GC, C#, por supuesto, pero eso es una bestia diferente. – Hogan

+1

Sí, la gestión de recursos a través de RAII (con o sin recuento de ref) no es realmente GC, es mucho más general. –

+0

Además, sucede en momentos predeterminados, GC no es determinista. – Hogan

Respuesta

3

Me encantan las clases de rasgos. No es exactamente específico de C++ (otros lenguajes como los tiene Scala), pero le permite adaptar objetos, básicamente para especificar un conjunto de operaciones que un tipo debería soportar. Imagine que quiere un "hasher", en el sentido de tr1::hash. hash se define para algunos tipos, pero no para otros. ¿Cómo se puede hacer una clase que tenga un hash definido para todos los tipos que desee? Se puede declarar una clase como:

template < typename T> 
struct hashing_tratis 
{ 
    typedef std::tr1::hash<T> hashing_type; 
}; 

que es, se espera que una clase que tiene la correcta hasing_type definido para ser utilizado. Sin embargo, el hash no se define, por ejemplo, para myType, por lo que puede escribir:

template <> 
struct hashing_traits<myType> 
{ 
    typedef class_that_knows_how_to_hash_myType hashing_type; 
}; 

De esta manera, supongamos que necesita una manera de hash de cualquier tipo que se utiliza en su programa (incluyendo myType).Puede escribir un hasher "universal" creando un rasgo de hasing:

template <typename T> 
struct something { 
    typename hashing_traits<T>::hashing_type hasher; 
    .... // here hasher is defined for all your relevant types, and knows how to hash them 
+0

Los lenguajes que admiten la reflexión pueden hacer esto sin la clase de rasgos engorrosos. Simplemente preguntan a la clase si admite una interfaz y la llama. Lo mismo también es posible en idiomas con soporte de función de primer orden. – jmucchiello

+1

jmucchiello: Bueno, no exactamente. ¿Qué ocurre, por ejemplo, cuando solicitas una clase o interfaz que este objeto no implementa? Tienes que "buscar" un adaptador. La parametrización de tipos en C++ permite esta construcción de rasgos con una interfaz homogénea. –

2

Bueno, esto se puede hacer en el lenguaje de programación D, así como los dos que mencionas, pero las plantillas lo suficientemente potentes como para funcionar básicamente como compilar el tiempo pato son bastante útiles. A menudo siento que una gran parte del debate entre los lenguajes estáticos y dinámicos se puede resolver con un sistema de plantillas suficientemente bueno, de modo que, aunque los tipos deben resolverse estáticamente en el momento de la compilación, no es necesario conocerlos en el momento del desarrollo. C++ fue pionero en esta idea (al menos entre los principales idiomas). D lo lleva varios pasos más allá.

Con plantillas y pato en tiempo de compilación, obtiene lo mejor de ambos mundos: la flexibilidad de no tener que especificar tipos en tiempo de desarrollo de una función o clase y la seguridad y el rendimiento de conocerlos en tiempo de compilación.

+0

La pregunta es específicamente sobre C++. –

+1

@Neil: Solo estoy señalando que no es ** completamente ** específico, aunque C++ es el lenguaje más popular en el que esta técnica está disponible. – dsimcha

1

Bueno, casi todos 'los patrones de diseño' tienen un fuerte vínculo a C++. Debería asumir que no tienen NADA que ver con 'diseño apropiado' y 'mejores prácticas' y TODO lo que tiene que ver con extrañas fallas de C++ y considerar cuidadosamente la necesidad real de cualquiera de ellos en lugar de complicar innecesariamente su código. Especialmente dado que muchos de los patrones de diseño son curitas para solucionar problemas creados por otros patrones de diseño. Esto se aplica diez veces más cuando se usa cualquier lenguaje que no sea C++, porque C++ tiene una gran cantidad de problemas que ningún otro lenguaje hace.

Por ejemplo, singleton es el ejemplo más obvio de esto.La razón real de esto es la forma en que C++ tiene una inicialización estática muy mal implementada. Sin esto, en realidad tienes que saber lo que estás haciendo para que la inicialización funcione correctamente, y la mayoría de las personas realmente no. Para la mayoría de los usos, la sobrecarga adicional de singleton está bien, pero debe tenerse en cuenta que tiene una sobrecarga, y no tiene uso en la mayoría de los idiomas. También crea problemas adicionales en cada implementación individual que he visto.

Lo mismo se aplica al patrón de puente, puedo ver el uso de singletons, pero simplemente no hay ninguna razón para utilizar el patrón de puente. Todo se reduce a '¿sabes lo que estás haciendo?'. Si es así, no necesita un patrón de puente. De lo contrario, probablemente deberías obtener más información antes de tratar de resolver el problema que te pide usar el patrón bridge. Lo más importante, no es realmente útil en idiomas además de C++. El objetivo es separar la implementación y la interfaz, que en los lenguajes OO más modernos ya se hace porque tienen módulos propios de algún tipo. En C++, es mejor usar interfaces virtuales puras para casos como este (y no creo que tenga un rendimiento peor, tiene un rendimiento mucho mejor que el patrón de puente combinado con plantillas).

Pero ese es el problema con los patrones de diseño; no es una metodología en absoluto, solo un montón de cosas al azar. Las cosas aleatorias que la mayoría de las personas que las defienden no las entienden realmente, la mayoría de las cuales no tienen ningún lugar para llamarlas con el diseño de palabras en ellas. No son herramientas de diseño, sino soluciones específicas para diversos problemas. Muchos de los cuales son evitables.

Irónicamente, la API de Java está llena de puentes y una amplia variedad de patrones de diseño que son totalmente inútiles en Java. La API de Java es fácil de usar la mayor parte del tiempo, pero la jerarquía sobrecomplicada puede ser muy engorrosa para trabajar.

+0

¿Por qué una buena implementación de patrón de puente con plantillas tiene peor rendimiento que uno basado en tablas virtuales? Además, los puentes no son solo una cuestión de módulos, la ocultación de la complejidad con un enfoque basado en capas es su principal valor independientemente del idioma. –

+0

informática. –

+1

Un nivel adicional de direccionamiento indirecto no afecta tanto al rendimiento como a los buenos hábitos de GRASP. Y no está respondiendo la pregunta. –

2
  • siempre que la persona que llama responsable de la memoria de asignación/desasignación, lo que significa que el código que se vería así en Java/C#:

    MyClass doSomething(MyClass someInstance); 
    

    se parece a esto en C/C++:

  • El uso de destructores para cerrar sockets, archivos mangos, etc.
+1

O 'boost :: shared_ptr doSomething (const MyClass & someInstance);' –

+1

Si desea que FORCE la persona que llama sea responsable de alloc/free, debe usar referencias: void doSomething (const myclass & in_param, myclass & out_param); No se puede llamar a doSomething con la asignación de out_param en algún lugar y pasándolo. – jmucchiello

+0

En realidad, es una mejor práctica asumir toda la responsabilidad de todas las asignaciones/dealloc que su código debería usar. Puede hacer esto al (1) usar punteros inteligentes que liberarán sus recursos asignados cuando no se usen más y (2) devolviendo un objeto por copia y liberando esa copia de sus recursos cuando se destruya. –

0

Pocos idiomas admiten multiple dispatch (siendo el más común double dispatch) como lo hace C++, puede hacerlo estática o dinámicamente.
Con él puede permitir que las instancias interactúen entre sí sin conocerlas ni probar su tipo concreto.

+0

Diría que casi TODOS los idiomas (a excepción de unos pocos como CLOS que lo soportan de forma nativa) lo soportan como lo hace C++, usted tiene que codificarlo usted mismo. –

+0

C# 4.0 lo hace! – Hogan

+0

@Hogan: Oh, tengo que buscar eso. Sólo dinámicamente, supongo? –

1

Existe la expresión pImpl. Funciona de manera similar al patrón Bridge.

Y Expression Template, lo que retrasa las evaluaciones de expresión de C++.

Estas dos obras también exploran esta pregunta: More C++ Idioms y Effective C++ (Scott Meyers, 2005)