usar resueltos nombres leer funciones ejercicios ejemplos como caracteres cadenas cadena arreglo almacenar c++ string inheritance stl

resueltos - funciones de cadenas de caracteres en c++



¿Por qué uno no debería derivar de la clase de cadena c++ std? (7)

Quería preguntar sobre un punto específico hecho en C ++ efectivo.

Dice:

Un destructor debería hacerse virtual si una clase necesita actuar como una clase polimórfica. Agrega además que dado que std::string no tiene un destructor virtual, nunca se debe derivar de él. También std::string ni siquiera está diseñado para ser una clase base, olvida la clase base polimórfica.

No entiendo qué se requiere específicamente en una clase para ser elegible para ser una clase base (no polimórfica)?

¿La única razón por la que no debería derivar de la clase std::string es que no tiene un destructor virtual? Para fines de reutilización, se puede definir una clase base y múltiples clases derivadas pueden heredar de ella. Entonces, ¿qué hace que std::string ni siquiera sea elegible como clase base?

Además, si hay una clase base puramente definida para fines de reutilización y hay muchos tipos derivados, ¿hay alguna forma de evitar que el cliente haga Base* p = new Derived() porque las clases no están destinadas a ser utilizadas polimórficamente?


¿Por qué uno no debería derivar de la clase de cadena c ++ std?

Porque no es necesario . Si desea usar DerivedString para la extensión de la funcionalidad; No veo ningún problema para derivar std::string . Lo único es que no debe interactuar entre ambas clases (es decir, no use string como un receptor para DerivedString ).

¿Hay alguna forma de evitar que el cliente haga Base* p = new Derived()

. Asegúrese de proporcionar envoltorios en inline alrededor Base métodos Base dentro de la clase Derived . p.ej

class Derived : protected Base { // ''protected'' to avoid Base* p = new Derived const char* c_str () const { return Base::c_str(); } //... };


Creo que esta declaración refleja la confusión aquí (el énfasis es mío):

No entiendo qué se requiere específicamente en una clase para ser elegible para ser un clas básico ( no polimórfico )?

En C ++ idiomático, hay dos usos para derivar de una clase:

  • herencia privada , utilizada para mixins y programación orientada a aspectos usando plantillas.
  • herencia pública , utilizada solo para situaciones polimórficas . EDITAR : Bien, supongo que esto podría usarse también en algunos escenarios de mixin, como boost::iterator_facade , que aparecen cuando el CRTP está en uso.

No hay absolutamente ninguna razón para derivar públicamente una clase en C ++ si no estás tratando de hacer algo polimórfico. El idioma viene con funciones gratuitas como característica estándar del idioma, y ​​las funciones gratuitas son las que debería usar aquí.

Piénselo de esta manera: ¿realmente desea obligar a los clientes de su código a convertir a usar una clase de cadena patentada simplemente porque quiere aplicar algunos métodos? Porque a diferencia de Java o C # (o la mayoría de los lenguajes orientados a objetos similares), cuando deriva una clase en C ++, la mayoría de los usuarios de la clase base necesitan saber acerca de ese tipo de cambio. En Java / C #, generalmente se accede a las clases a través de referencias, que son similares a los punteros de C ++. Por lo tanto, hay un nivel de indirección involucrado que desacopla a los clientes de su clase, lo que le permite sustituir una clase derivada sin que los demás clientes lo sepan.

Sin embargo, en C ++, las clases son tipos de valores , a diferencia de la mayoría de los demás lenguajes de OO. La forma más fácil de ver esto es lo que se conoce como el problema de corte . Básicamente, considere:

int StringToNumber(std::string copyMeByValue) { std::istringstream converter(copyMeByValue); int result; if (converter >> result) { return result; } throw std::logic_error("That is not a number."); }

Si transfiere su propia cadena a este método, se llamará al constructor de copia de std::string para que haga una copia, no al constructor de copia para su objeto derivado , sin importar qué clase secundaria de std::string se pase. Esto puede llevar a la inconsistencia entre sus métodos y cualquier cosa adjunta a la cadena. La función StringToNumber no puede simplemente tomar cualquiera que sea su objeto derivado y copiar eso, simplemente porque su objeto derivado probablemente tiene un tamaño diferente que std::string , pero esta función fue compilada para reservar solo el espacio para una std::string almacenamiento automático En Java y C # esto no es un problema porque el único almacenamiento automático involucrado son los tipos de referencia, y las referencias son siempre del mismo tamaño. No es así en C ++.

Para resumir, no use la herencia para virar en los métodos en C ++. Eso no es idiomático y genera problemas con el lenguaje. Use funciones que no sean de amigo o que no sean miembros, donde sea posible, seguido de composición. No use herencia a menos que sea una metaprogramación de plantillas o desee un comportamiento polimórfico. Para obtener más información, consulte el artículo 23 de Cype Effective Cype de Scott Meyers: Prefiera las funciones no miembro no miembro a las funciones miembro.

EDITAR: Aquí hay un ejemplo más completo que muestra el problema de corte. Puedes ver su resultado en codepad.org

#include <ostream> #include <iomanip> struct Base { int aMemberForASize; Base() { std::cout << "Constructing a base." << std::endl; } Base(const Base&) { std::cout << "Copying a base." << std::endl; } ~Base() { std::cout << "Destroying a base." << std::endl; } }; struct Derived : public Base { int aMemberThatMakesMeBiggerThanBase; Derived() { std::cout << "Constructing a derived." << std::endl; } Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; } ~Derived() { std::cout << "Destroying a derived." << std::endl; } }; int SomeThirdPartyMethod(Base /* SomeBase */) { return 42; } int main() { Derived derivedObject; { //Scope to show the copy behavior of copying a derived. Derived aCopy(derivedObject); } SomeThirdPartyMethod(derivedObject); }


El estándar de C ++ establece que si el destructor de la clase Base no es virtual y usted borra un objeto de la clase Base que apunta al objeto de una clase derivada, entonces causa un comportamiento indefinido.

Sección estándar C ++ 5.3.5 / 3:

si el tipo estático del operando es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del operando y el tipo estático tendrá un destructor virtual o el comportamiento no está definido.

Para ser claros en la clase no polimórfica y la necesidad de destructor virtual
El propósito de hacer un destructor virtual es facilitar la eliminación polimórfica de objetos a través de la expresión de eliminación. Si no hay eliminación polimórfica de objetos, entonces no necesita destructor virtual.

¿Por qué no derivar de la clase String?
En general, se debe evitar derivar de cualquier clase de contenedor estándar por la sencilla razón de que no tienen destructores virtuales, lo que hace que sea imposible eliminar objetos de forma polimórfica.
En cuanto a la clase de cadena, la clase de cadena no tiene ninguna función virtual, por lo que no hay nada que pueda anular. Lo mejor que puedes hacer es esconder algo.

Si desea tener una funcionalidad tipo cadena, debe escribir una clase propia en lugar de heredar de std :: string.


Hay dos razones simples para no derivar de una clase no polimórfica:

  • Técnico : introduce errores de corte (porque en C ++ pasamos por valor a menos que se especifique lo contrario)
  • Funcional : si no es polimórfico, puede lograr el mismo efecto con la composición y el reenvío de funciones.

Si desea agregar nuevas funcionalidades a std::string , primero considere usar funciones gratuitas (posiblemente plantillas), como lo hace la biblioteca Boost String Algorithm .

Si desea agregar nuevos miembros de datos, ajuste adecuadamente el acceso de clase incrustándolo (Composición) dentro de una clase de su propio diseño.

EDITAR :

@Tony notó correctamente que la razón Funcional que cité probablemente no tenía sentido para la mayoría de las personas. Existe una regla general, en buen diseño, que dice que cuando puede elegir una solución entre varias, debe considerar la que tiene el acoplamiento más débil. La composición tiene un acoplamiento más débil que la herencia, y por lo tanto debe preferirse, cuando sea posible.

Además, la composición le da la oportunidad de ajustar bien el método de clase del original. Esto no es posible si elige la herencia (pública) y los métodos no son virtuales (que es el caso aquí).


No solo el destructor no es virtual, std :: string no contiene funciones virtuales y no tiene miembros protegidos. Eso hace que sea muy difícil para la clase derivada modificar su funcionalidad.

Entonces, ¿por qué te beneficiarías de eso?

Otro problema al ser no polimórfico es que si pasas la clase derivada a una función que espera un parámetro de cadena, tu funcionalidad adicional se cortará y el objeto volverá a verse como una cadena simple.


Para ofrecer el lado contrario a la recomendación general (que es sólida cuando no hay problemas de verbosidad / productividad en particular) ...

Escenario para uso razonable

Hay al menos un escenario donde la derivación pública de bases sin destructores virtuales puede ser una buena decisión:

  • desea algunos de los beneficios de seguridad de tipo y legibilidad de códigos proporcionados por tipos dedicados definidos por el usuario (clases)
  • una base existente es ideal para almacenar los datos, y permite operaciones de bajo nivel que el código del cliente también querría utilizar
  • desea la conveniencia de reutilizar funciones que respaldan esa clase base
  • usted entiende que cualquier invariante adicional que sus datos necesiten lógicamente solo se puede aplicar en el código que accede explícitamente a los datos como el tipo derivado, y dependiendo de la medida en que eso ocurra "naturalmente" en su diseño, y de cuánto puede confiar el cliente Para comprender y cooperar con las invariantes lógicamente ideales, es posible que desee que las funciones de los miembros de la clase derivada reverifiquen las expectativas (y arrojen o lo que sea)
  • la clase derivada agrega algunas funciones de conveniencia altamente específicas que operan sobre los datos, tales como búsquedas personalizadas, filtrado / modificación de datos, transmisión, análisis estadístico, iteradores (alternativos)
  • el acoplamiento del código de cliente a la base es más apropiado que el acoplamiento a la clase derivada (ya que la base es estable o los cambios reflejan mejoras a la funcionalidad también esenciales para la clase derivada)
    • Dicho de otra manera: quiere que la clase derivada continúe exponiendo la misma API que la clase base, incluso si eso significa que el código del cliente está forzado a cambiar, en lugar de aislarlo de alguna manera que permita que las API base y derivada crezcan. de sincronización
  • no vas a mezclar punteros a objetos base y derivados en partes del código responsable de eliminarlos

Esto puede parecer bastante restrictivo, pero hay muchos casos en programas del mundo real que coinciden con este escenario.

Discusión de fondo: méritos relativos

La programación se trata de compromisos. Antes de escribir un programa conceptualmente "correcto":

  • considerar si se requiere una complejidad y un código adicionales que ofusque la lógica real del programa, y ​​por lo tanto, es más propenso a errores en general a pesar de manejar un problema específico de manera más robusta,
  • sopesar los costos prácticos con la probabilidad y las consecuencias de los problemas, y
  • Considere el "retorno de la inversión" y qué otra cosa podría estar haciendo con su tiempo.

Si los problemas potenciales implican el uso de los objetos que simplemente no puede imaginar que alguien intente, dado su conocimiento sobre su accesibilidad, alcance y naturaleza del uso en el programa , o puede generar errores en tiempo de compilación para uso peligroso (por ejemplo, una afirmación de que el tamaño de clase derivado coincide con el de la base, lo que evitaría agregar nuevos miembros de datos), entonces cualquier otra cosa puede ser una sobreingeniería prematura. Tome la victoria fácil en un diseño y código limpio, intuitivo y conciso.

Razones para considerar la derivación sans virtual destructor

Digamos que tiene una clase D derivada públicamente de B. Sin esfuerzo, las operaciones en B son posibles en D (con la excepción de la construcción, pero incluso si hay muchos constructores, a menudo puede proporcionar un reenvío efectivo al tener una plantilla para cada número distinto de argumentos de constructor: por ejemplo, template <typename T1, typename T2> D(const T1& x1, const T2& t2) : B(t1, t2) { } . Mejor solución generalizada en C ++ 0x plantillas variadas.)

Además, si B cambia, de manera predeterminada, D expone esos cambios (permaneciendo sincronizados), pero alguien puede necesitar revisar la funcionalidad extendida introducida en D para ver si sigue siendo válida y el uso del cliente.

Reformándose esto: existe un acoplamiento explícito reducido entre la clase base y la derivada, pero un mayor acoplamiento entre la base y el cliente .

A menudo, esto NO es lo que quiere, pero a veces es ideal, y otras veces no es un problema (vea el siguiente párrafo). Los cambios en la base fuerzan más cambios en el código del cliente en los lugares distribuidos a lo largo de la base del código, y algunas veces las personas que cambian la base pueden no tener siquiera acceso al código del cliente para revisarlo o actualizarlo de manera correspondiente. Sin embargo, a veces es mejor: si usted como proveedor de la clase derivada, el "hombre en el medio", quiere que los cambios de la clase base se transmitan a los clientes, y generalmente quiere que los clientes puedan, algunas veces forzados, actualizar su código cuando el la clase base cambia sin que usted tenga que estar constantemente involucrado, entonces la derivación pública puede ser ideal. Esto es común cuando su clase no es tanto una entidad independiente por derecho propio, sino un valor agregado delgado para la base.

Otras veces, la interfaz de la clase base es tan estable que el acoplamiento puede considerarse como un problema. Esto es especialmente cierto en clases como contenedores estándar.

En resumen, la derivación pública es una manera rápida de obtener o aproximar la interfaz ideal y familiar de la clase derivada, de una manera que es concisa y evidentemente correcta tanto para el desarrollador como para el programador del cliente, con funcionalidades adicionales disponibles como funciones miembro ( que en mi humilde opinión (que obviamente difiere con Sutter, Alexandrescu, etc.) puede ayudar a la usabilidad, la legibilidad y ayudar a las herramientas que mejoran la productividad, incluidos los IDE)

Estándares de codificación C ++ - Sutter y Alexandrescu - contras examinados

El artículo 35 de las Normas de codificación de C ++ enumera los problemas con el escenario derivado de std::string . Según van los escenarios, es bueno que ilustre la carga de exponer una API grande pero útil, pero buena y mala, ya que la API base es notablemente estable: forma parte de la Biblioteca estándar. Una base estable es una situación común, pero no más común que una volátil y un buen análisis debería relacionarse con ambos casos. Al considerar la lista de cuestiones del libro, contrastaré específicamente la aplicabilidad de los problemas con los casos de, digamos:

a) class Issue_Id : public std::string { ...handy stuff... }; <- derivación pública, nuestro uso controvertido
b) class Issue_Id : public string_with_virtual_destructor { ...handy stuff... }; <- derivación de OO más segura
c) class Issue_Id { public: ...handy stuff... private: std::string id_; }; class Issue_Id { public: ...handy stuff... private: std::string id_; }; <- un enfoque compositivo
d) usando std::string todas partes, con funciones de soporte independientes

(Esperamos poder aceptar que la composición es una práctica aceptable, ya que proporciona encapsulación, tipo de seguridad y una API potencialmente enriquecida además de std::string ).

Entonces, digamos que está escribiendo un nuevo código y comienza a pensar en las entidades conceptuales en un sentido OO. Tal vez en un sistema de seguimiento de errores (estoy pensando en JIRA), uno de ellos es decir un Issue_Id. El contenido de los datos es textual, y consiste en una identificación alfabética del proyecto, un guión y un número creciente de problemas: por ejemplo, "MYAPP-1234". Los id. De problemas se pueden almacenar en std::string , y habrá muchas búsquedas de texto poco escrupulosas y operaciones de manipulación necesarias en los identificadores de incidencias, un gran subconjunto de los que ya se proporcionan en std::string y algunos más para una buena medida ( por ejemplo, obtener el componente de identificación del proyecto, proporcionando la siguiente identificación de problema posible (MYAPP-1235)).

En la lista de problemas de Sutter y Alexandrescu ...

Las funciones de no miembros funcionan bien dentro del código existente que ya manipula string . Si, en cambio, usted proporciona una super_string , fuerza los cambios a través de su base de código para cambiar los tipos y las firmas de función a super_string .

El error fundamental con este reclamo (y la mayoría de los siguientes) es que promueve la conveniencia de usar solo algunos tipos, ignorando los beneficios de la seguridad del tipo. Expresa una preferencia por d) anterior, en lugar de una idea de c) o b) como alternativas a a). El arte de la programación implica equilibrar los pros y los contras de los distintos tipos para lograr una reutilización, rendimiento, conveniencia y seguridad razonables. Los párrafos siguientes detallan esto.

Con la derivación pública, el código existente puede acceder implícitamente a la string clase base como una string y continuar comportándose como siempre. No hay ninguna razón específica para pensar que el código existente desee utilizar ninguna funcionalidad adicional de super_string (en nuestro caso Issue_Id) ... de hecho, a menudo es un código de soporte de nivel inferior que preexiste a la aplicación para la que está creando el super_string y, por lo tanto, ajeno a las necesidades previstas por las funciones ampliadas. Por ejemplo, supongamos que hay una función no miembro to_upper(std::string&, std::string::size_type from, std::string::size_type to) ; aún podría aplicarse a Issue_Id .

Entonces, a menos que la función de soporte no miembro se esté limpiando o extendiendo a un costo deliberado de acoplarlo estrechamente al nuevo código, entonces no necesita ser tocado. Si se está revisando para admitir identificadores de problemas (por ejemplo, utilizando la información del formato de contenido de datos para caracteres alfabéticos principales en mayúsculas), entonces probablemente sea bueno asegurarse de que realmente se pase un Issue_Id creando una sobrecarga. ala to_upper(Issue_Id&) y apegarse a los enfoques de derivación o composición que permiten la seguridad del tipo. Si se super_string o composición, no hay diferencia en esfuerzo o mantenibilidad. No es probable que una función de soporte autónomo reutilizable to_upper_leading_alpha_only(std::string&) sea ​​de mucha utilidad. No recuerdo la última vez que quise tal función.

El impulso de usar std::string todas partes no es cualitativamente diferente a aceptar todos sus argumentos como contenedores de variantes o void* s, por lo que no tiene que cambiar las interfaces para aceptar datos arbitrarios, pero hace que la implementación sea propensa a errores y menos código de auto-documentación y compilable-verificable.

Las funciones de interfaz que toman una cadena ahora necesitan: a) mantenerse alejadas de la funcionalidad agregada de super_string (inútil); b) copie su argumento a super_string (derrochador); o c) emite la referencia de cadena a una referencia super_string (incómoda y potencialmente ilegal).

Esto parece estar revisando el primer punto: código antiguo que necesita ser refactorizado para usar la nueva funcionalidad, aunque esta vez el código del cliente en lugar del código de soporte. Si la función quiere comenzar a tratar su argumento como una entidad para la cual las nuevas operaciones son relevantes, entonces debería comenzar tomando sus argumentos como ese tipo y los clientes deberían generarlos y aceptarlos usando ese tipo. Los mismos problemas existen para la composición. De lo contrario, c) puede ser práctico y seguro si se siguen las pautas que enumero a continuación, aunque es feo.

Las funciones de miembro de super_string no tienen más acceso a las partes internas de la cadena que las funciones que no son miembros porque la cadena probablemente no tiene miembros protegidos (recuerde, de hecho, no estaba destinado a ser derivado)

Es cierto, pero a veces eso es algo bueno. Muchas clases base no tienen datos protegidos. La interfaz pública de string es todo lo que se necesita para manipular los contenidos, y una funcionalidad útil (por ejemplo, get_project_id() postulada anteriormente) se puede expresar de forma elegante en términos de esas operaciones. Conceptualmente, muchas veces me he derivado de los contenedores estándar, he querido no ampliar o personalizar su funcionalidad a lo largo de las líneas existentes, ya que son contenedores "perfectos", sino que he querido agregar otra dimensión de comportamiento que es específica a mi aplicación, y no requiere acceso privado. Es porque ya son buenos contenedores que son buenos para reutilizar.

Si super_string oculta algunas de las funciones de string (y redefinir una función no virtual en una clase derivada no está anulando), eso podría causar confusión generalizada en el código que manipula string que comenzaron su vida convertidas automáticamente de super_string s.

También es cierto para la composición, y es más probable que suceda ya que el código no pasa por omisión para pasar las cosas y, por lo tanto, permanecer sincronizado, y también es cierto en algunas situaciones con jerarquías polimórficas en tiempo de ejecución también. Funciones nombradas Samed que se comportan de manera diferente en las clases que inicialmente parecen intercambiables, simplemente asquerosas. Esta es la precaución habitual para la programación correcta de OO, y de nuevo no es motivo suficiente para abandonar los beneficios en seguridad tipo, etc.

¿Qué super_string si super_string desea heredar de la string para agregar más estado [explicación de la división]

Estoy de acuerdo, no es una buena situación, y en algún lugar en el que personalmente tiendo a dibujar la línea ya que a menudo mueve los problemas de eliminación a través de un puntero a la base del ámbito de la teoría a la práctica, los destructores no se invocan para miembros adicionales. Aún así, cortar puede hacer lo que se quiera, dado el enfoque de derivar super_string para no cambiar su funcionalidad heredada, sino para agregar otra "dimensión" de funcionalidad específica de la aplicación ....

Es cierto que es tedioso tener que escribir funciones de paso a paso para las funciones de miembro que desea mantener, pero dicha implementación es mucho mejor y más segura que usar herencia pública o no pública.

Bueno, ciertamente estoy de acuerdo con el tedio ...

Directrices para la derivación exitosa sin destructor virtual

  • idealmente, evite agregar miembros de datos en la clase derivada: las variantes de división pueden eliminar accidentalmente miembros de datos, corromperlos, no inicializarlos ...
  • aún más: evite miembros de datos que no sean POD: eliminación mediante puntero de clase base es un comportamiento técnicamente indefinido, pero con tipos no POD que no ejecutan sus destructores es más probable que tengan problemas no teóricos con pérdidas de recursos, recuentos de referencias incorrectos etc.
  • honrar al sustituto de Liskov Principal / no se pueden mantener sólidamente nuevas invariantes
    • por ejemplo, al derivar de std::string no puede interceptar algunas funciones y esperar que sus objetos permanezcan en mayúscula: cualquier código que acceda a ellas mediante std::string& o ...* puede usar std::string ''s implementaciones de funciones originales para cambiar el valor)
    • derivar para modelar una entidad de nivel superior en su aplicación, para extender la funcionalidad heredada con alguna funcionalidad que utiliza pero no está en conflicto con la base; no espere ni intente cambiar las operaciones básicas y el acceso a esas operaciones otorgadas por el tipo de base
  • tenga en cuenta el acoplamiento: la clase base no se puede eliminar sin afectar el código del cliente, incluso si la clase base evoluciona para tener una funcionalidad inadecuada, es decir, la usabilidad de la clase derivada depende de la adecuación continua de la base
    • a veces, incluso si usa la composición, deberá exponer el miembro de datos debido al rendimiento, problemas de seguridad del hilo o falta de semántica de valores, por lo que la pérdida de encapsulación de la derivación pública no es tangiblemente peor
  • cuanto mayor sea la probabilidad de que las personas que usan la clase potencialmente derivada desconozcan sus compromisos de implementación, menos pueden permitirse convertirlos en peligrosos.
    • por lo tanto, las bibliotecas de bajo nivel ampliamente implementadas con muchos usuarios ocasionales ad-hoc deberían ser más cautelosas con la derivación peligrosa que el uso localizado por los programadores que usan rutinariamente la funcionalidad a nivel de aplicación y / o en implementaciones / bibliotecas "privadas"

Resumen

Dicha derivación no está exenta de problemas, así que no la considere a menos que el resultado final justifique los medios. Dicho esto, rechazo rotundamente cualquier afirmación de que esto no se puede usar de manera segura y apropiada en casos particulares; es solo una cuestión de dónde trazar la línea.

Experiencia personal

A veces derivo de std::map<> , std::vector<> , std::string etc. - Nunca me he quemado por los problemas de cortar o eliminar por medio de punteros de clase base, y he ahorró mucho tiempo y energía para cosas más importantes. No almaceno tales objetos en contenedores heterogéneos polimórficos. Sin embargo, debe considerar si todos los programadores que utilizan el objeto son conscientes de los problemas y es probable que se programen en consecuencia. Personalmente, me gusta escribir mi código para usar el polimorfismo dinámico y de tiempo de ejecución solo cuando sea necesario, mientras que algunas personas (debido a los fondos Java, su enfoque preferido para gestionar dependencias de recompilación o cambiar entre comportamientos de tiempo de ejecución, instalaciones de prueba, etc.) los usan habitualmente y por lo tanto, debe estar más preocupado por las operaciones seguras a través de punteros de clase base.


Si realmente quieres derivar de esto (no discutir por qué quieres hacerlo), creo que puedes evitar la creación de instancias directas de heap de clase Derived haciendo que su operator new privado:

class StringDerived : public std::string { //... private: static void* operator new(size_t size); static void operator delete(void *ptr); };

Pero de esta forma se restringe a sí mismo de cualquier objeto dinámico StringDerived .