¿Qué hace C ++ mejor que D?


135

Recientemente he estado aprendiendo D y estoy empezando a familiarizarme con el idioma. Sé lo que ofrece, aún no sé cómo usar todo, y no sé mucho sobre modismos D, etc., pero estoy aprendiendo.

Me gusta D. Es un lenguaje agradable, que es, de alguna manera, una gran actualización de C, y está bien hecho. Ninguna de las características parece estar "atornillada", pero en realidad está bastante bien pensada y bien diseñada.

A menudo escuchará que D es lo que C ++ debería haber sido (dejo la pregunta de si eso es cierto para todos y cada uno para decidir por sí mismos para evitar guerras de llamas innecesarias). También he oído de varios programadores de C ++ que disfrutan de D mucho más que C ++.

Yo mismo, aunque sé C, no puedo decir que sé C ++. Me gustaría saber de alguien que conozca tanto C ++ como D si cree que hay algo que C ++ hace mejor que D como lenguaje (lo que significa que no es el habitual "tiene más bibliotecas de terceros" o "hay más recursos" o " existen más trabajos que requieren C ++ que D ").

D fue diseñado por algunos programadores de C ++ muy hábiles ( Walter Bright y Andrei Alexandrescu , con la ayuda de la comunidad D) para solucionar muchos de los problemas que tenía C ++, pero ¿había algo que en realidad no mejorara después de todo? Algo que se perdió? ¿Algo que crees que no fue una mejor solución?

Además, tenga en cuenta que estoy hablando de D 2.0 , no de D 1.0 .


15
Me aseguré de que la comunidad D vea esto, ya que estoy seguro de que hay muchos más desarrolladores de C ++ que desarrolladores de D por aquí. De esa manera, tendrás respuestas más interesantes o al menos variadas.
Klaim

77
Además, D2 fue diseñado por Walter Bright pero también con Alexandrescu. Es posible que desee arreglar eso en su pregunta.
Klaim

2
@Klaim: hubo (y todavía hay) mucha participación de la comunidad en D y en la biblioteca estándar también.
Michal Minich

28
@Anto Como lenguaje, C ++ es mucho mejor que D para que tú, el programador, odies tu vida.
Arlen

66
@jokoon: En realidad, sí, con muy poco trabajo: digitalmars.com/d/2.0/interfaceToC.html
Anto

Respuestas:


124

La mayoría de las cosas que C ++ "hace" mejor que D son cosas meta: C ++ tiene mejores compiladores, mejores herramientas, bibliotecas más maduras, más enlaces, más expertos, más tutoriales, etc. Básicamente tiene más y mejores de todas las cosas externas que usted esperaría de un lenguaje más maduro. Esto es indiscutible.

En cuanto al lenguaje en sí, en mi opinión, hay algunas cosas que C ++ hace mejor que D. Probablemente haya más, pero aquí hay algunas que puedo enumerar en la parte superior de mi cabeza:

C ++ tiene un sistema de tipos mejor pensado
Hay algunos problemas con el sistema de tipos en D en este momento, que parecen ser descuidos en el diseño. Por ejemplo, actualmente es imposible copiar una estructura const en una estructura no const si la estructura contiene referencias a objetos de clase o punteros debido a la transitividad de const y la forma en que los constructores postblit trabajan en tipos de valores. Andrei dice que sabe cómo resolver esto, pero no dio ningún detalle. El problema es ciertamente solucionable (la introducción de constructores de copia estilo C ++ sería una solución), pero actualmente es un problema importante en el lenguaje.

Otro problema que me ha molestado es la falta de constante lógica (es decir, no mutablecomo en C ++). Esto es excelente para escribir código seguro para subprocesos, pero hace que sea difícil (¿imposible?) Realizar una inicialización perezosa dentro de los objetos const (piense en una función const 'get' que construye y almacena en caché el valor devuelto en la primera llamada).

Por último, teniendo en cuenta estos problemas existentes, estoy preocupado por la forma en que el resto del sistema de tipos ( pure, shared, etc.) va a interactuar con todo lo demás en el idioma una vez que se ponen en uso. La biblioteca estándar (Phobos) actualmente hace muy poco uso del sistema de tipos avanzado de D, por lo que creo que es razonable preguntarse si se mantendrá bajo estrés. Soy escéptico, pero optimista.

Tenga en cuenta que C ++ tiene algunas verrugas del sistema de tipos (p. Ej., Constantes no transitivas, que iteratortambién requieren const_iterator) que lo hacen bastante feo, pero si bien el sistema de tipos de C ++ es un poco incorrecto en algunas partes, no le impide realizar el trabajo como D a veces lo hace.

Editar: Para aclarar, creo que C ++ tiene un sistema de tipos mejor pensado , no necesariamente uno mejor, si eso tiene sentido. Esencialmente, en DI sentimos que existe un riesgo involucrado en el uso de todos los aspectos de su sistema de tipos que no están presentes en C ++.

D a veces es demasiado conveniente
Una crítica que a menudo escucha de C ++ es que oculta algunos problemas de bajo nivel, por ejemplo, tareas simples como a = b;podría estar haciendo muchas cosas como llamar a operadores de conversión, llamar a operadores de asignación de sobrecarga, etc., que pueden ser difícil de ver desde el código. A algunas personas les gusta esto, a otras no. De cualquier manera, en D es peor (mejor?), Debido a cosas como opDispatch, @property, opApply, lazyque tienen el potencial de cambiar el código de aspecto inocente en cosas que no te esperas.

No creo que este sea un gran problema personalmente, pero algunos podrían encontrar esto desagradable.

D requiere recolección de basura
Esto podría verse como controvertido porque es posible ejecutar D sin el GC. Sin embargo, solo porque sea posible no significa que sea práctico. Sin un GC, se pierden muchas características de D, y usar la biblioteca estándar sería como caminar en un campo minado (¿quién sabe qué funciones asignan memoria?). Personalmente, creo que es totalmente impráctico usar D sin un GC, y si no eres fanático de los GC (como yo), entonces esto puede ser bastante desagradable.

Las definiciones
ingeniosas de la matriz en D asignan memoria Esta es una de mis principales molestias:

int[3] a = [1, 2, 3]; // in D, this allocates then copies
int a[3] = {1, 2, 3}; // in C++, this doesn't allocate

Aparentemente, para evitar la asignación en D, debe hacer:

static const int[3] staticA = [1, 2, 3]; // in data segment
int[3] a = staticA; // non-allocating copy

Estas pequeñas asignaciones 'a sus espaldas' son buenos ejemplos de mis dos puntos anteriores.

Editar: Tenga en cuenta que este es un problema conocido en el que se está trabajando.
Editar: esto ya está solucionado. No se realiza ninguna asignación.

Conclusión
Me he centrado en los aspectos negativos de D vs C ++ porque eso es lo que se hizo la pregunta, pero no veas esta publicación como una declaración de que C ++ es mejor que D. Podría fácilmente hacer una publicación más grande de lugares donde D es mejor que C ++. Depende de usted decidir cuál usar.


Miré a D hace unos años (antes de 2.0). La recolección de basura no era realmente necesaria entonces, estaba allí por defecto, pero podría optar por un código de bajo nivel. Lo que pensé que estaba mal sobre esto es que no pude encontrar la forma de volver a optar. Por ejemplo, en un contenedor basado en árbol, el código de la biblioteca puede administrar la memoria de los nodos del árbol. El árbol en su conjunto todavía puede ser coleccionable, con IIRC un destructor que recoge todos esos nodos. Pero los objetos a los que hacen referencia los datos en ese contenedor también deben ser coleccionables: debe haber un gancho para marcar todos los elementos de datos en el árbol para el GC.
Steve314

3
Todavía puede deshabilitar el GC para el código de bajo nivel: Peter dice que el idioma actualmente depende mucho de él. Además, puede indicarle al GC que escanee rangos fuera de su montón administrado utilizando su API: GC.addRange de core.memory .
Vladimir Panteleev

+1 para señalar que la biblioteca estándar D es basura recolectada y que el código de desactivación de GC no es una interoperabilidad perfecta. No es algo en lo que haya pensado, pero parece un gran obstáculo a superar.
masonk

132

Cuando me uní al desarrollo D estaba en la posición peculiar de ser una de las personas que más sabe sobre C ++. Ahora estoy en una posición aún más peculiar para ser también una de las personas que más sabe sobre D. No estoy diciendo esto para apropiarse de los derechos de crédito o alardear tanto como para comentar que estoy curiosamente posición ventajosa para responder a esta pregunta. Lo mismo se aplica a Walter.

En general, preguntar qué hace C ++ (y con eso quiero decir que C ++ 2011) hace mejor que D es tan contradictorio como la pregunta: "Si le paga a un profesional para que limpie su casa, ¿cuáles son los lugares que dejarán? más sucio que antes? Lo que sea de valor fue que C ++ podía hacer lo que D no podía hacer, siempre ha sido un dolor de pulgar para mí y para Walter, por lo que casi por definición no hay nada que C ++ pueda hacer que no esté al alcance de D.

Una cosa que rara vez se entiende en el diseño del lenguaje (porque pocas personas tienen la suerte de hacer algo) es que hay muchos menos errores no forzados de lo que parece. Muchos de nosotros, los usuarios del lenguaje, miramos una construcción u otra y decimos: "¡Ew! ¡Esto está muy mal! ¿Qué estaban pensando?" El hecho es que la mayoría de los casos incómodos en un lenguaje son consecuencia de algunas decisiones fundamentales que son sólidas y deseables, pero que compiten o se contradicen entre sí (por ejemplo, modularidad y eficiencia, concisión y control, etc.).

Con todo esto en mente, enumeraré algunas cosas en las que puedo pensar, y para cada una explicaré cómo la elección de D se deriva del deseo de cumplir algún otro estatuto de nivel superior.

  1. D asume que todos los objetos son móviles mediante copia bit a bit. Esto deja una minoría de diseños a C ++, específicamente aquellos que usan punteros internos, es decir, una clase que contiene punteros dentro de sí mismo. (Cualquier diseño de este tipo puede traducirse sin costo o con un costo de eficiencia insignificante a D, pero habría un esfuerzo de traducción involucrado). Tomamos esta decisión para simplificar en gran medida el lenguaje, hacer que la copia de objetos sea más eficiente con la mínima o nula intervención del usuario, y evitar toda la copia de la construcción del pantano y las referencias rvalue aparecen por completo.

  2. D no permite los tipos de género ambiguos (que no pueden decidir si son tipos de valor o de referencia). Dichos diseños se rechazan por unanimidad en C ++ y casi siempre son incorrectos, pero algunos de ellos son técnicamente correctos. Tomamos esta decisión porque no permite la mayoría de los códigos incorrectos y solo una pequeña fracción de código correcto que se puede rediseñar. Creemos que es una buena compensación.

  3. D no permite jerarquías de múltiples raíces. Un póster anterior aquí se entusiasmó mucho con este tema en particular, pero este es un terreno bien pisado y no hay una ventaja palpable de las jerarquías sin raíz sobre las jerarquías que tienen una raíz común.

  4. En D no puedes lanzar, por ejemplo, un int. Debes lanzar un objeto que herede Throwable. No hay duda de que el estado de cosas es mejor en D, pero, bueno, es una cosa que C ++ puede hacer que D no puede hacer.

  5. En C ++, la unidad de encapsulación es una clase. En D es un módulo (es decir, un archivo). Walter tomó esta decisión por dos razones: mapear naturalmente la encapsulación a la semántica de protección del sistema de archivos, y obviar la necesidad de un "amigo". Esta elección se integra muy bien dentro del diseño de modularidad general de D. Sería posible cambiar las cosas para que se parecieran más a C ++, pero eso forzaría las cosas; Las opciones de alcance de encapsulación de C ++ son buenas solo para el diseño físico de C ++.

Podría haber una o dos cosas más pequeñas, pero en general debería ser así.


66
@DeadMG: Para que eso funcione en C ++, el objeto que se está moviendo necesitaría un puntero hacia atrás al objeto que lo apunta (para que pueda actualizarse durante la construcción de la copia). Si ese es el caso, en D puede usar el constructor postblit para actualizar el puntero de todos modos. No discuta contra D si solo tiene un conocimiento pasajero de ello.
Peter Alexander

13
@Peter: ¿Debería ser una referencia aunque su vida útil esté estrictamente basada en el alcance? ¿Debo desperdiciar la sobrecarga de asignarlo dinámicamente y la indirección y los gastos generales de caché y colección porque quiero alias? Además, espero que el recolector pueda recolectarlo de manera determinista, para una semántica equivalente. Eso es claramente no tener el control.
DeadMG

3
@sbi: la existencia de una clase superior no afecta en absoluto sus elecciones. En la red de tipo de clase, siempre hay una parte superior e inferior. La parte inferior solo es explícita en algunos idiomas . La parte superior (es decir, Objeto, etc.) es explícita en más idiomas. Estos tipos siempre existen en concepto; cuando también son accesibles, simplemente ofrecen algunas facilidades adicionales para el usuario del idioma sin causar problemas.
Andrei Alexandrescu

66
@quant_dev: te alegrará saber que hay un proyecto GSoC que ya está en una buena forma centrado en el álgebra lineal de alto rendimiento con BLAS. También proporciona implementaciones "ingenuas" de las primitivas apropiadas para fines de prueba y evaluación comparativa. Para responder a su segunda pregunta, Java establece un nivel bastante bajo para comparar bibliotecas numéricas. Siempre tendrá el problema de pasar una barrera JNI para acceder a las librerías de álgebra de alto rendimiento, y la sintaxis será mala porque Java carece de sobrecarga del operador.
Andrei Alexandrescu

44
@ PeterAlexander: DeadMG está justo en el punto. "No debe usar punteros para valorar los tipos" ignora claramente el hecho de que los punteros en cualquier idioma generalmente se usan con los tipos de valores (¿realmente espera ver un uso Object*tan extendido como un int*?) Y D parece ignorar por completo la penalización de rendimiento, o alegar que no existe. Eso es obviamente falso: la pérdida de caché es bastante notable en muchos casos, por lo que C ++ siempre tendrá esa ventaja de flexibilidad sobre D.
Mehrdad

65

Creo que va a ser muy difícil encontrar mucho en D, que es objetivamentepeor que C ++. La mayoría de los problemas con D en los que podría decir objetivamente que es peor son problemas de calidad de implementación (que generalmente se deben a lo joven que es el lenguaje y la implementación y se han solucionado a una velocidad vertiginosa últimamente), o son problemas con la falta de bibliotecas de terceros (que vendrá con el tiempo). El lenguaje en sí mismo es generalmente mejor que C ++, y los casos en que C ++, como lenguaje, es mejor, generalmente van a ser donde hay una compensación donde C ++ fue de una manera y D fue de otra, o donde alguien tiene razones subjetivas de por qué piensa que uno es mejor que otro. Pero la cantidad de razones objetivas directas por las cuales C ++, como lenguaje, es mejor es probable que sean pocas y distantes entre sí.

De hecho, tengo que destrozar mi cerebro para encontrar razones por las cuales C ++, como lenguaje, es mejor que D. Lo que generalmente viene a la mente es una cuestión de compensaciones.

  1. Debido a que la constante de D es transitiva, y debido a que el lenguaje es inmutable , tiene garantías mucho más fuertes que las de C ++ const, lo que significa que D no tiene y no puede tener mutable. No puede tener const lógico . Entonces, obtienes una gran ganancia con el sistema const de D, pero en algunas situaciones, simplemente no puedes usar constcomo lo harías en C ++.

  2. D tiene solo un operador de conversión, mientras que C ++ tiene 4 (5 si cuenta el operador de conversión C). Esto hace que lidiar con los yesos en D sea más fácil en el caso general, pero es problemático cuando realmente quieres las complicaciones / beneficios adicionales que const_castproporcionan y sus hermanos. Pero D es lo suficientemente potente como para poder usar plantillas para implementar los moldes de C ++, por lo que si realmente los quiere, puede tenerlos (e incluso pueden terminar en la biblioteca estándar de D en algún momento).

  3. D tiene muchas menos conversiones implícitas que C ++ y es mucho más probable que declare que dos funciones están en conflicto entre sí (lo que le obliga a ser más específico sobre cuáles de las funciones quiere decir, ya sea con conversiones o dando la ruta completa del módulo ) A veces, eso puede ser molesto, pero evita todo tipo de problemas de secuestro de funciones . Sabes que realmente estás llamando a la función que quieres.

  4. El sistema de módulos de D es mucho más limpio que los #incluye de C ++ (sin mencionar que es mucho más rápido en la compilación), pero carece de cualquier tipo de espacio de nombres más allá de los propios módulos. Entonces, si desea un espacio de nombres dentro de un módulo, debe seguir la ruta de Java y usar funciones estáticas en una clase o estructura. Funciona, pero si realmente quieres espacios de nombres, obviamente no es tan limpio como el espacio de nombres real. Sin embargo, para la mayoría de las situaciones, el espacio de nombres que le proporcionan los módulos mismos es suficiente (y bastante sofisticado cuando se trata de cosas como conflictos en realidad).

  5. Al igual que Java y C #, D tiene herencia única en lugar de herencia múltiple, pero a diferencia de Java y C #, le brinda algunas formas fantásticas de obtener el mismo efecto sin todos los problemas que tiene la herencia múltiple de C ++ (y la herencia múltiple de C ++ puede volverse muy desordenada). a veces). D no solo tiene interfaces , sino que también tiene mixins de cadena , mixins de plantilla y alias . Por lo tanto, el resultado final es posiblemente más poderoso y no tiene todos los problemas que tiene la herencia múltiple de C ++.

  6. Similar a C #, D separa estructuras y clases . Las clases son tipos de referencia que tienen herencia y se derivan de ellas Object, mientras que las estructuras son tipos de valor sin herencia. Esta separación puede ser tanto buena como mala. Elimina el clásico problema de corte en C ++ y ayuda a separar los tipos que realmente son de valor de los que se supone que son polimórficos, pero al principio, al menos, la distinción puede ser molesta para un programador de C ++. En última instancia, hay una serie de beneficios, pero te obliga a lidiar con tus tipos de manera algo diferente.

  7. Las funciones miembro de las clases son polimórficas por defecto. No puede declararlos no virtuales . Depende del compilador decidir si pueden serlo (lo que realmente es el caso si son finales y no anulan una función de una clase base). Entonces, eso podría ser un problema de rendimiento en algunos casos. Sin embargo, si realmente no necesita el polimorfismo, entonces todo lo que tiene que hacer es usar estructuras , y no es un problema.

  8. D tiene un recolector de basura incorporado . Muchos de C ++ considerarían que es un inconveniente grave, y la verdad sea dicha, en la actualidad, su implementación podría requerir un trabajo serio. Ha estado mejorando, pero definitivamente todavía no está a la par con el recolector de basura de Java. Sin embargo, esto se ve mitigado por dos factores. Uno, si está utilizando principalmente estructuras y otros tipos de datos en la pila, entonces no es un gran problema. Si su programa no está asignando y desasignando constantemente cosas en el montón, estará bien. Y dos, puede omitir el recolector de basura si lo desea y simplemente usar C mallocy free. Hay algunas características del lenguaje (como el corte de matriz)) que tendrá que evitar o tener cuidado, y parte de la biblioteca estándar no es realmente utilizable sin al menos algún uso del GC (particularmente el procesamiento de cadenas), pero puede escribir en D sin usar el recolector de basura si realmente quieres Lo más inteligente es probablemente usarlo en general y luego evitarlo cuando el perfil muestre que está causando problemas para el código crítico de rendimiento, pero puede evitarlo por completo si lo desea. Y la calidad de la implementación del GC mejorará con el tiempo, eliminando muchas de las preocupaciones que puede causar el uso de un GC . Entonces, en última instancia, el GC no será un problema tan grande y, a diferencia de Java, puede evitarlo si lo desea.

Probablemente también haya otros, pero eso es lo que puedo encontrar en este momento. Y si te das cuenta, todas son compensaciones. D eligió hacer algunas cosas de manera diferente a C ++ que tienen ventajas definitivas sobre cómo C ++ las hace pero también tienen algunas desventajas. Lo que es mejor depende de lo que esté haciendo, y en muchos casos probablemente solo parecerá peor al principio y luego no tendrá ningún problema una vez que se acostumbre. En todo caso, los problemas en D generalmente serán nuevos causados ​​por cosas nuevas que otros lenguajes no han hecho antes o no han hecho de la misma manera que D lo ha hecho. En general, D ha aprendido muy bien de los errores de C ++.

Y D, como lenguaje, mejora sobre C ++ de tantas maneras que creo que generalmente es el caso de que D es objetivamente mejor.

  1. D tiene compilación condicional . Esta es una de las características que frecuentemente extraño cuando estoy programando en C ++. Si C ++ lo agregara, entonces C ++ mejoraría a pasos agigantados cuando se trata de cosas como plantillas.

  2. D tiene reflexión en tiempo de compilación .

  3. Las variables son locales de subprocesos de forma predeterminada, pero pueden serlo sharedsi así lo desea. Esto hace que tratar con hilos sea mucho más limpio que en C ++. Estás en completo control. Puede usar immutabley pasar mensajes para comunicarse entre hilos, o puede hacer variables sharedy hacerlo a la manera C ++ con mutexes y variables de condición. Incluso eso se mejora sobre C ++ con la introducción de sincronizado (similar a C # y Java). Entonces, la situación de subprocesos de D es mucho mejor que la de C ++.

  4. Las plantillas de D son mucho más potentes que las plantillas de C ++, lo que le permite hacer mucho más, mucho más fácilmente. Y con la adición de restricciones de plantilla, los mensajes de error son mucho mejores que en C ++. D hace plantillas muy potentes y utilizables. No es casualidad que el autor de Modern C ++ Design sea ​​uno de los principales colaboradores de D. Considero que las plantillas de C ++ son muy deficientes en comparación con las plantillas de D, y a veces puede ser muy frustrante cuando se programa en C ++.

  5. D tiene programación de contrato incorporada .

  6. D tiene un marco de prueba de unidad incorporado .

  7. D tiene soporte incorporado para unicode con string(UTF-8), wstring(UTF-16) y dstring(UTF-32). Facilita el manejo de Unicode. Y si solo desea usar stringy, en general, no preocuparse por Unicode, puede hacerlo, aunque cierta comprensión de los conceptos básicos de Unicode ayuda con algunas de las funciones estándar de la biblioteca.

  8. La sobrecarga de operadores de D es mucho mejor que la de C ++, lo que le permite utilizar una función para sobrecargar múltiples operadores al mismo tiempo. Un buen ejemplo de esto es cuando necesita sobrecargar los operadores aritméticos básicos y sus implementaciones son idénticas, salvo para el operador. Los mixins de cadena lo hacen muy fácil, lo que le permite tener una definición de función simple para todos ellos.

  9. Las matrices de D son mucho mejores que las matrices de C ++. No solo son del tipo adecuado con una longitud, sino que se pueden agregar y cambiar de tamaño. Concatenarlos es fácil. Y lo mejor de todo, tienen rodajas . Y eso es una gran ayuda para el procesamiento de matriz eficiente. Las cadenas son matrices de caracteres en D, y no es un problema (de hecho, ¡es genial!), Porque las matrices de D son muy poderosas.

Podría seguir y seguir. Muchas de las mejoras que D proporciona son pequeñas cosas (como usar thispara nombres de constructor o no permitir si las declaraciones o los cuerpos de bucle donde un punto y coma es todo su cuerpo), pero algunos de ellos son bastante grandes, y cuando los agrega todos, hace una experiencia de programación mucho mejor. C ++ 0x agrega algunas de las características que tiene D que faltaba en C ++ (por ejemplo, autoy lambdas), pero incluso con todas sus mejoras, todavía no habrá mucho que sea objetivamente mejor acerca de C ++ como lenguaje que D.

No hay duda de que hay muchas razones subjetivas para agradar una sobre la otra, y la relativa inmadurez de la implementación de D puede ser un problema a veces (aunque ha estado mejorando muy rápidamente últimamente, especialmente desde que los repositorios se movieron a Github ) , y la falta de bibliotecas de terceros definitivamente puede ser un problema (aunque el hecho de que D pueda llamar fácilmente a las funciones de C y, en menor medida, a las funciones de C ++ , definitivamente mitiga el problema). Pero esos son problemas de calidad de implementación en lugar de problemas con el lenguaje en sí. Y a medida que se solucionen los problemas de calidad de implementación, será mucho más agradable usar D.

Entonces, supongo que la respuesta corta a esta pregunta es "muy poco". D, como lenguaje, es generalmente superior a C ++.


2
Los lenguajes de recolección de basura usan 2-5 veces más memoria que los que no son GC (según la charla de Alexandrescu en YT), por lo que definitivamente es un problema si ese (uso de memoria) es el cuello de botella.
NoSenseEtAl

9

Uso de memoria RAII y pila

D 2.0 no permite que suceda RAII en la pila porque eliminó el valor de la scopepalabra clave al asignar instancias de clase en la pila.

No puede hacer una herencia de tipo de valor en D, de manera tan efectiva que lo obliga a hacer una asignación de montón para cualquier forma de RAII.
Es decir, a menos que lo use emplace, pero es muy doloroso de usar, ya que debe asignar la memoria a mano. (Todavía tengo que encontrarlo práctico para usar emplaceen D.)


6

C ++ es mucho mejor para obligarlo a ser detallado. Esto podría ser mejor o peor en tus ojos, dependiendo de si te gusta la inferencia o la verbosidad.

Compare la memorización en tiempo de ejecución en C ++ :

template <typename ReturnType, typename... Args>
function<ReturnType (Args...)> memoize(function<ReturnType (Args...)> func)
{
    map<tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable {
            tuple<Args...> t(args...);
            return cache.find(t) == cache.end()
                ? cache[t] : cache[t] = func(args...);
    });
}

con lo mismo en D:

auto memoize(F)(F func)
{
    alias ParameterTypeTuple!F Args;
    ReturnType!F[Tuple!Args] cache;
    return (Args args)
    {
        auto key = tuple(args);
        return key in cache ? cache[key] : (cache[key] = func(args));
    };
}

Observe, por ejemplo, la verbosidad adicional con template <typename ReturnType, typename... Args>versus (F), Args...versus Args, args...versus args, etc.
Para bien o para mal, C ++ es más detallado.

Por supuesto, también puedes hacer esto en D:

template memoize(Return, Args...)
{
    Return delegate(Args) memoize(Return delegate(Args) func)
    {
        Return[Tuple!Args] cache;
        return delegate(Args args)
        {
            auto key = tuple(args);
            return key in cache ? cache[key] : (cache[key] = func(args));
        };
    }
}

y se verían casi idénticos, pero esto requeriría un delegate, mientras que el original aceptaba cualquier objeto invocable. (La versión C ++ 0x requiere unstd::function objeto, de cualquier manera, es más detallado y restrictivo en sus entradas ... lo que podría ser bueno si le gusta la verbosidad, malo si no lo hace).


2

No sé mucho acerca de D, pero a muchos, muchos programadores de C ++ que conozco les disgusta mucho, y personalmente tengo que estar de acuerdo: no me gusta el aspecto de D y no voy a tomar uno más cercano.

Para entender por qué D no está ganando más tracción, debe comenzar por comprender qué atrae a las personas a C ++. En una palabra, la razón número uno es el control. Cuando programa en C ++, tiene control total sobre su programa. ¿Quiere reemplazar la biblioteca estándar? Usted puede. ¿Quieres hacer lanzamientos de punteros inseguros? Usted puede. ¿Quieres violar la corrección const? Usted puede. ¿Quiere reemplazar el asignador de memoria? Usted puede. ¿Desea copiar alrededor de la memoria sin tener en cuenta su tipo? Si de verdad quieres. ¿Quieres heredar de múltiples implementaciones? Es tu funeral. Demonios, incluso puedes obtener bibliotecas de recolección de basura, como el recolector Boehm. Luego tiene problemas como el rendimiento, que sigue de cerca el control: cuanto más control tiene un programador, más optimizado puede hacer su programa.

Aquí hay algunas cosas que he visto al investigar un poco y hablar con un par de personas que lo han probado:

Jerarquía de tipos unificada. Los usuarios de C ++ usan la herencia muy raramente, la mayoría de los programadores de C ++ prefieren la composición, y los tipos solo deben vincularse a través de la herencia si hay una muy buena razón para hacerlo. El concepto de Objeto viola este principio fuertemente al vincular cada tipo. Además, está violando uno de los principios más básicos de C ++: solo usa lo que desea. Al no tener la opción de heredar de Object, y los costos que conlleva, están muy en contra de lo que C ++ representa como lenguaje en términos de dar al programador control sobre su programa.

He escuchado sobre problemas con funciones y delegados. Aparentemente, D tiene funciones y delegados como tipos de función invocables en tiempo de ejecución, y no son lo mismo pero son intercambiables o ... ¿algo? Mi amigo tuvo bastantes problemas con ellos. Esta es definitivamente una degradación de C ++, que simplemente tiene std::functiony ya está.

Entonces tienes compatibilidad. D no es particularmente compatible con C ++. Quiero decir, ningún lenguaje es compatible con C ++, seamos sinceros, excepto C ++ / CLI, que es una especie de trampa, pero como barrera de entrada, hay que mencionarlo.

Entonces, hay algunas otras cosas. Por ejemplo, solo lea la entrada de Wikipedia.

import std.metastrings;
pragma(msg, Format!("7! = %s", fact_7));
pragma(msg, Format!("9! = %s", fact_9));

printfes una de las funciones menos seguras jamás desarrolladas, en la misma familia que los grandes problemas, como los getsde la antigua biblioteca C Standard. Si lo busca en Stack Overflow, encontrará muchas, muchas preguntas relacionadas con su mal uso. Fundamentalmente, printfes una violación de DRY- está dando el tipo en la cadena de formato, y luego lo vuelve a dar cuando le da un argumento. Una violación de DRY donde, si se equivoca, suceden cosas muy malas, por ejemplo, si cambia un typedef de un entero de 16 bits a uno de 32 bits. Tampoco es extensible, imagínese lo que sucedería si todos inventaran sus propios especificadores de formato. Los iostreams de C ++ pueden ser lentos, y su elección de operador puede no ser la mejor, y su interfaz podría usar el trabajo, pero están fundamentalmente garantizados para ser seguros, y DRY no se viola, y se pueden extender fácilmente. Esto no es algo de lo que se pueda decir printf.

Sin herencia múltiple. Esa no es la forma de C ++. Los programadores de C ++ esperan tener un control completo sobre su programa y el lenguaje que impone lo que no puede heredar es una violación de ese principio. Además, hace que la herencia (aún más) sea frágil, porque si cambia un tipo de una interfaz a una clase porque desea proporcionar una implementación predeterminada o algo así, de repente se rompe el código de su usuario. Eso no es algo bueno.

Otro ejemplo es stringy wstring. En C ++ ya es bastante doloroso tener que convertir entre ellos, y esta biblioteca admite Unicode, y esta antigua biblioteca C solo usa const char*, y tener que escribir diferentes versiones de la misma función dependiendo del tipo de argumento de cadena que desee. En particular, los encabezados de Windows, por ejemplo, tienen algunas macros extremadamente irritantes para hacer frente al problema que a menudo puede interferir con su propio código. Agregar dstringa la mezcla solo empeorará las cosas, ya que ahora, en lugar de dos tipos de cadenas, debe administrar tres. Tener más de un tipo de cadena aumentará los dolores de mantenimiento e introducirá código repetitivo que trata con cadenas.

Scott Meyers escribe:

D es un lenguaje de programación creado para ayudar a los programadores a abordar los desafíos del desarrollo moderno de software. Lo hace fomentando módulos interconectados a través de interfaces precisas, una federación de paradigmas de programación estrechamente integrados, aislamiento de subprocesos impuesto por el lenguaje, seguridad de tipo modular, un modelo de memoria eficiente y más.

El aislamiento de hilos forzado por el lenguaje no es una ventaja. Los programadores de C ++ esperan un control total sobre sus programas, y el lenguaje que obliga a algo definitivamente no es lo que ordenó el médico.

También voy a mencionar la manipulación de cadenas en tiempo de compilación. D tiene la capacidad de interpretar el código D en tiempo de compilación. Esto no es una ventaja. Considere los dolores de cabeza masivos causados ​​por el preprocesador relativamente limitado de C, conocido por todos los programadores veteranos de C ++, y luego imagine cuán gravemente se abusará de esta característica. La capacidad de crear código D en tiempo de compilación es excelente, pero debe ser semántica , no sintáctica.

Además, puede esperar un cierto reflejo. D tiene recolección de basura, que los programadores de C ++ asociarán con lenguajes como Java y C # que se oponen bastante directamente a ella en filosofías, y las similitudes sintácticas también los recordarán. Esto no es necesariamente objetivamente justificable, pero es algo que ciertamente debe tenerse en cuenta.

Básicamente, no ofrece mucho que los programadores de C ++ no puedan hacer. Quizás sea más fácil escribir un metaprograma factorial en D, pero ya podemos escribir metaprogramas factoriales en C ++. Tal vez en D pueda escribir un rastreador de rayos en tiempo de compilación, pero nadie quiere hacerlo de todos modos. En comparación con las violaciones fundamentales de la filosofía de C ++, lo que puede hacer en D no es particularmente notable.

Incluso si estas cosas son solo problemas en la superficie, entonces estoy bastante seguro de que el hecho de que, en la superficie, D no se parezca a C ++ en absoluto, es probablemente una buena razón por la que muchos programadores de C ++ no están migrando a D. Quizás D necesita hacer un mejor trabajo publicitándose.


99
@DeadMG: es 100% incorrecto y se pierde el punto de decir que esto definitivamente es una degradación de C ++, que simplemente ha std::functionterminado y ya está. ¿Por qué? Porque también, por ejemplo, tiene punteros de función. Es exactamente lo mismo en D: las "funciones" de D son punteros de función, y los "delegados" de D son los mismos que los de C ++ std::function(excepto que están integrados). No hay "rebaja" en ninguna parte, y hay una correspondencia 1: 1 entre ellos, por lo que no debería ser confuso si está familiarizado con C ++.
Mehrdad

10
@ Mark Trapp: Debo admitir que no entiendo muy bien tu postura sobre el tema: los comentarios no se deben utilizar para, ya sabes, comentar una respuesta.
klickverbot

66
@ Mark Trapp: Mi punto es que la mayoría de los comentarios aquí no eran obsoletos (la meta discusión que vinculó específicamente se aplica a las sugerencias que ya se han incorporado a la publicación original), pero señaló imprecisiones de hecho en la publicación, que todavía están presentes .
klickverbot

77
Una nota sobre el formato: la función de formato de D es segura para los tipos (resuelve los problemas de seguridad / desbordamiento) y no viola DRY ya que la cadena de formato solo especifica cómo deben formatearse los argumentos, no sus tipos. Esto es posible gracias a las variables de typesafe de D. Por lo tanto, esa objeción al menos es completamente inválida.
Justin W

17
@ Mark: Cualquiera que sea la política actual con respecto a ellos, me parece estúpido e impide que se eliminen las discusiones de comentarios . Creo que esta respuesta tuvo extensas discusiones (que ahora me interesa), pero no estoy seguro, y no tengo forma de averiguarlo. Esa sala a la que se vinculó tiene más de 10k mensajes, y no tengo ninguna posibilidad de encontrar una discusión que parece recordar haber tenido lugar, pero no puedo recordar el contenido. Las discusiones con respecto a esta respuesta pertenecen aquí, a esta respuesta , y no a alguna sala de chat, donde podrían mezclarse en discusiones sobre sexo, drogas y rock and roll.
sbi

1

Una cosa que aprecio en C ++ es la capacidad de documentar un argumento de función o un valor de retorno como una referencia de C ++ en lugar de un puntero, lo que implica tomar un no nullvalor.

Versión D:

class A { int i; }

int foo(A a) {
    return a.i; // Will crash if a is null
}

int main() {
    A bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    return foo(bar);
}

Versión C ++:

class A { int i; };

int foo(A& a) {
    return a.i; // Will probably not crash since
                // C++ references are less likely
                // to be null.
}

int main() {
    A* bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    // Hm.. I have to dereference the bar-pointer
    // here, otherwise it wont compile.  Lets add
    // a check for null before.
    if (bar)
        return foo(*bar);
    return 0;
}

Para ser justos, puede acercarse mucho a C ++ convirtiéndose Aen una D structy marcando el foo()argumento-como ref(las clases son tipos de referencia y las estructuras son tipos de valor en D, similar a C #).

Creo que hay un plan para crear una NonNullableplantilla para las clases como una construcción de biblioteca estándar D en su lugar. Aun así, me gusta la brevedad de solo en Type&comparación con NonNullable(Type), y preferiría no anulable como valor predeterminado (representando algo como Typey Nullable(Type)). Pero es demasiado tarde para cambiar eso por D y ahora me estoy desviando del tema.


3
Tanto los argumentos de función como los valores de retorno en D pueden marcarse refpara darle el mismo efecto que los de C ++ &. La principal diferencia es que refno tomará un tiempo temporal, incluso si lo es const.
Jonathan M Davis

Me gusta la forma en que está prohibido devolver referencias a variables locales en D, no estaba al tanto de eso hasta que leí tu comentario. Pero aún puede devolver una referencia nula no local sin pensar en ella de una manera en que el operador de desreferencia C lo haga pensar.
lumor

Estás confundiendo cosas. Las clases son siempre referencias, y eso es independiente de la referencia. Las referencias en D son como referencias en Java. Son punteros manejados. Pasar o regresar por ref es como pasar o regresar con & en C ++. Pasar una referencia de clase por ref es como pasar un puntero en C ++ con & (por ejemplo, A * &). Las clases no van a la pila. Sí, NonNullable permitiría tener una referencia de clase que se garantizó que no es nula, pero que está completamente separada de la referencia. Lo que intenta hacer en el código C ++ no funciona en D porque las clases no van a la pila. Las estructuras lo hacen.
Jonathan M Davis

1
Entonces, sí, poder tener una referencia de clase que no sea nullabe sería bueno, pero C ++ logra hacer lo que está mostrando porque permite que las clases estén en la pila y permite que los punteros sean desreferenciados. Y aunque puede desreferenciar los punteros en D, las clases son referencias, no punteros, por lo que no puede desreferenciarlos. Dado que no puede ponerlos en la pila, y no puede desreferenciarlos, no hay forma integrada en D para tener una clase que no pueda ser nula. Que es una pérdida, pero NonNullable lo arreglará, y las ganancias derivadas de la separación de las estructuras y las clases son generalmente mayores de todos modos.
Jonathan M Davis

2
Las referencias de C ++ no pueden ser nulas según el estándar del lenguaje (decir "probablemente no será nulo" es incorrecto ya que no puede ser nulo). Desearía que hubiera una manera de no permitir nulo como un valor válido para una clase.
jsternberg

1

Lo más importante que C ++ "hace mejor" que D es interactuar con bibliotecas heredadas . Varios motores 3D, OpenCL y similares. Como D es nuevo, tiene una cantidad mucho menor de bibliotecas diferentes para elegir.

Otra diferencia importante entre el C ++ y el D es que el C ++ tiene múltiples proveedores financieramente independientes, pero a partir de 2014, el D tiene prácticamente solo uno , el equipo de 2 personas que lo creó. Es interesante que el "segundo principio fuente" que dice que los proyectos nunca deberían depender de la tecnología, los componentes, que tienen un solo proveedor único, parece ser válido incluso para el software.

A modo de comparación, la primera versión del intérprete de Ruby fue escrita por el inventor de Ruby, el Yukihiro Matsumoto, pero el intérprete de Ruby principal de la era 2014 ha sido escrito prácticamente desde cero por otras personas. Por lo tanto, Ruby puede verse como un lenguaje que tiene más de un proveedor financieramente independiente. D, por otro lado, puede ser una tecnología increíble, pero depende de los pocos desarrolladores que la desarrollen.

La historia de Java muestra que incluso si una tecnología, en este caso, Java, tiene un buen, pero único, financiero, existe un gran riesgo de que la tecnología sea esencialmente abandonada, independientemente de la gran base de usuarios corporativos. Una cita de la Apache Software Foundation , donde la CE significa "Comité Ejecutivo":

Oracle proporcionó a la CE una solicitud y licencia de especificación Java SE 7 que son contradictorias, restringen severamente la distribución de implementaciones independientes de la especificación y, lo más importante, prohíben la distribución de implementaciones independientes de código abierto de la especificación.

Como nota histórica, se puede decir que los applets de Java tenían un lienzo 3D acelerado por hardware años antes de que se desarrollara HTML5 WebGL. El problema de la velocidad de inicio de los applets de Java podría haberse resuelto, si el Java hubiera sido un proyecto comunitario, pero los ejecutivos del único financiero de Java, Sun Microsystems, no consideraron lo suficientemente importante como para arreglar la implementación de Java. El resultado final: lienzo HTML5 de múltiples proveedores como una "réplica de pobre" de los marcos de la GUI de Java (Swing, etc.). Curiosamente, en el lado del servidor, el lenguaje de programación Python tiene las mismas ventajas que prometió Java: escribir una vez, ejecutar en todos los servidores, independientemente del hardware, siempre que la aplicación Python no esté compilada en código máquina. El Python es tan viejo / joven como el Java, pero a diferencia del Java, '

Resumen:

Al evaluar la tecnología para el uso de producción, las propiedades más importantes de las tecnologías son las personas, quienes la desarrollan, el proceso social, el lugar donde se lleva a cabo el desarrollo y la cantidad de equipos de desarrollo financieramente independientes.

Si es más ventajoso usar la tecnología T1 sobre la tecnología T2 depende de los proveedores de las tecnologías y si la tecnología T1 permite resolver problemas relacionados con proyectos de manera más económica que T2. Por ejemplo, si se ignora el problema del proveedor único, entonces, para los sistemas de información, Java sería una tecnología "mejor" que C ++, porque los binarios de Java no necesitan recompilación en la implementación en el nuevo hardware y la cantidad de trabajo de desarrollo de software relacionado con la administración de memoria es más pequeño para Java que para C ++. Los proyectos que se desarrollan desde cero, por ejemplo, proyectos que no dependen de otras bibliotecas, pueden ser más baratos de desarrollar en D que en C ++, pero, por otro lado, C ++ tiene más de un proveedor y, por lo tanto, es menos riesgoso a largo plazo . (El ejemplo de Java, donde Sun Microsystems estaba casi bien,

Una posible solución a algunas de las limitaciones de C ++

Una de las posibles soluciones a algunas de las limitaciones de C ++ es usar un patrón de diseño, donde la tarea inicial se hace pedazos y las piezas se "clasifican" (clasificadas, agrupadas, divididas, otras-buenas-palabras-para- lo mismo) a 2 clases: controlar tareas relacionadas con la lógica , donde los patrones de acceso a la memoria no permiten la localidad; tareas similares al procesamiento de señales , donde la localidad se logra fácilmente. Las tareas "similares" de procesamiento de señales se pueden implementar como un conjunto de programas de consola relativamente simplistas. Las tareas relacionadas con la lógica de control se colocan todas en un solo script que está escrito en Ruby o Python o de otro modo, donde la comodidad del desarrollo de software tiene mayor prioridad que la velocidad.

Para evitar la costosa inicialización del proceso del sistema operativo y la copia de datos entre los procesos del sistema operativo, el conjunto de pequeñas aplicaciones de consola C ++ podría implementarse como un solo programa C ++ que Ruby / Python / etc arranca como un "servlet". guión. El Ruby / Python / etc. El script cierra el servlet antes de salir. Comunicación entre el "servlet" y el Ruby / Python / etc. La secuencia de comandos se lleva a cabo en una canalización con nombre de Linux o algún mecanismo similar.

Si la tarea inicial no se presta para dividirse fácilmente en partes que pueden clasificarse en las 2 clases antes mencionadas, entonces una cosa a intentar podría ser reformular el problema para que la tarea inicial cambie.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.