Ok, esto es realmente difícil de confesar, pero tengo una fuerte tentación en este momento para heredar std::vector
.
Necesito unos 10 algoritmos personalizados para el vector y quiero que sean directamente miembros del vector. Pero, naturalmente, también quiero tener el resto de std::vector
la interfaz. Bueno, mi primera idea, como ciudadano respetuoso de la ley, era tener un std::vector
miembro en MyVector
clase. Pero luego tendría que volver a proporcionar manualmente toda la interfaz std :: vector. Demasiado para escribir. Luego, pensé en la herencia privada, de modo que en lugar de proporcionar métodos, escribiría un montón de using std::vector::member
's en la sección pública. Esto es tedioso también en realidad.
Y aquí estoy, realmente creo que simplemente puedo heredar públicamente std::vector
, pero dar una advertencia en la documentación de que esta clase no debe usarse polimórficamente. Creo que la mayoría de los desarrolladores son lo suficientemente competentes como para entender que esto no debería usarse polimórficamente de todos modos.
¿Es mi decisión absolutamente injustificable? Si es así, ¿por qué? ¿Puede proporcionar una alternativa que tendría los miembros adicionales en realidad miembros pero que no implicaría volver a escribir toda la interfaz del vector? Lo dudo, pero si puedes, seré feliz.
Además, aparte del hecho de que algún idiota puede escribir algo como
std::vector<int>* p = new MyVector
¿Hay algún otro peligro realista al usar MyVector? Al decir realista, descarto cosas como imaginar una función que toma un puntero para vectorizar ...
Bueno, he declarado mi caso. He pecado. Ahora depende de ti perdonarme o no :)
std::vector
La interfaz de @Jim: es bastante grande, y cuando aparezca C ++ 1x, se expandirá considerablemente. Eso es mucho para escribir y más para expandirse en unos pocos años. Creo que esta es una buena razón para considerar la herencia en lugar de la contención, si se sigue la premisa de que esas funciones deberían ser miembros (lo cual dudo). La regla para no derivar de los contenedores STL es que no son polimórficos. Si no los está utilizando de esa manera, no se aplica.Respuestas:
En realidad, no hay nada malo con la herencia pública de
std::vector
. Si necesitas esto, solo hazlo.Sugeriría hacerlo solo si es realmente necesario. Solo si no puede hacer lo que quiere con funciones gratuitas (por ejemplo, debe mantener algún estado).
El problema es que
MyVector
es una entidad nueva. Significa que un nuevo desarrollador de C ++ debería saber qué demonios es antes de usarlo. ¿Cuál es la diferencia entrestd::vector
yMyVector
? ¿Cuál es mejor usar aquí y allá? ¿Qué pasa si necesito moverstd::vector
aMyVector
? ¿Puedo usarswap()
o no?No produzca nuevas entidades solo para hacer que algo se vea mejor. Estas entidades (especialmente, tan comunes) no van a vivir en el vacío. Vivirán en ambientes mixtos con entropía constantemente incrementada.
fuente
MyVector
y luego intente pasarlos a funciones que aceptenstd::vector&
ostd::vector*
. Si hay algún tipo de asignación de copia involucrada usando std :: vector * o std :: vector &, tenemos problemas de corte donde los nuevos miembros de datosMyVector
no se copiarán. Lo mismo sería cierto para llamar a swap a través de un puntero / referencia base. Tiendo a pensar que cualquier tipo de jerarquía de herencia que corre el riesgo de segmentar objetos es mala.std::vector
El destructor no lo esvirtual
, por lo tanto, nunca debes heredar de élTodo el STL fue diseñado de tal manera que los algoritmos y los contenedores están separados .
Esto condujo a un concepto de diferentes tipos de iteradores: iteradores constantes, iteradores de acceso aleatorio, etc.
Por lo tanto, le recomiendo que acepte esta convención y diseñe sus algoritmos de tal manera que no les importe en qué contenedor están trabajando , y solo requerirían un tipo específico de iterador que necesitarían para realizar su operaciones
Además, déjame redirigirte a algunos buenos comentarios de Jeff Attwood .
fuente
La razón principal para no heredar
std::vector
públicamente es la ausencia de un destructor virtual que efectivamente evite el uso polimórfico de descendientes. En particular, no se le permite adelete
unstd::vector<T>*
que realmente apunta a un objeto derivado (incluso si la clase derivada no agrega miembros), sin embargo, el compilador generalmente no puede advertirle al respecto.La herencia privada está permitida bajo estas condiciones. Por lo tanto, recomiendo usar la herencia privada y reenviar los métodos requeridos del padre como se muestra a continuación.
Primero debe considerar refactorizar sus algoritmos para abstraer el tipo de contenedor en el que están operando y dejarlos como funciones con plantilla libre, como lo señala la mayoría de los que responden. Esto generalmente se hace haciendo que un algoritmo acepte un par de iteradores en lugar de contenedor como argumentos.
fuente
vector
El almacenamiento asignado no es el problema: después de todo,vector
el destructor se llamaría directamente a través de un puntero avector
. Es solo que el estándar prohíbedelete
los objetos de la tienda libre a través de una expresión de clase base. Seguramente, la razón es que el mecanismo de (des) asignación puede tratar de inferir el tamaño del fragmento de memoria para liberarlo deldelete
operando, por ejemplo, cuando hay varias arenas de asignación para objetos de ciertos tamaños. Esta restricción, afaics, no se aplica a la destrucción normal de objetos con una duración de almacenamiento estático o automático.Si está considerando esto, claramente ya ha matado a los pedantes de idiomas en su oficina. Con ellos fuera del camino, ¿por qué no simplemente hacer
Eso evitará todos los errores posibles que puedan surgir de la transmisión accidental de su clase MyVector, y aún puede acceder a todas las operaciones de vectores simplemente agregando un poco
.v
.fuente
¿Qué esperas lograr? ¿Solo proporciona alguna funcionalidad?
La forma idiomática de C ++ de hacer esto es simplemente escribir algunas funciones gratuitas que implementan la funcionalidad. Lo más probable es que realmente no necesite un std :: vector, específicamente para la funcionalidad que está implementando, lo que significa que realmente está perdiendo la reutilización al intentar heredar de std :: vector.
Le recomiendo encarecidamente que mire la biblioteca y los encabezados estándar y que medite sobre cómo funcionan.
fuente
front
yback
también. :) (Considere también el ejemplo de gratisbegin
yend
en C ++ 0x y boost.)Creo que muy pocas reglas deben seguirse ciegamente el 100% del tiempo. Parece que lo has pensado bastante y estás convencido de que este es el camino a seguir. Entonces, a menos que alguien presente buenas razones específicas para no hacer esto, creo que debe seguir adelante con su plan.
fuente
No hay ninguna razón para heredar a
std::vector
menos que uno quiera hacer una clase que funcione de manera diferente astd::vector
, porque maneja a su manera los detalles ocultos destd::vector
la definición, o a menos que uno tenga razones ideológicas para usar los objetos de dicha clase en lugar destd::vector
De los. Sin embargo, los creadores del estándar en C ++ no proporcionaronstd::vector
ninguna interfaz (en forma de miembros protegidos) que dicha clase heredada podría aprovechar para mejorar el vector de una manera específica. De hecho, no tenían forma de pensar en ningún aspecto específico que pudiera necesitar extensión o afinar la implementación adicional, por lo que no tenían que pensar en proporcionar una interfaz de este tipo para ningún propósito.Los motivos de la segunda opción pueden ser solo ideológicos, porque los
std::vector
s no son polimórficos, y de lo contrario no hay diferencia si exponestd::vector
la interfaz pública a través de la herencia pública o de la membresía pública. (Suponga que necesita mantener algún estado en su objeto para que no pueda salirse con las funciones libres). En una nota menos sólida y desde el punto de vista ideológico, parece que losstd::vector
s son una especie de "idea simple", por lo que cualquier complejidad en forma de objetos de diferentes clases posibles en su lugar ideológicamente no sirve de nada.fuente
En términos prácticos: si no tiene ningún miembro de datos en su clase derivada, no tiene ningún problema, ni siquiera en el uso polimórfico. Solo necesita un destructor virtual si los tamaños de la clase base y la clase derivada son diferentes y / o tiene funciones virtuales (lo que significa una tabla v).
PERO en teoría: desde [expr.delete] en el C ++ 0x FCD: en la primera alternativa (eliminar objeto), si el tipo estático del objeto a eliminar es diferente de su tipo dinámico, el tipo estático será un clase base del tipo dinámico del objeto que se va a eliminar y el tipo estático tendrá un destructor virtual o el comportamiento no está definido.
Pero puede derivar en privado de std :: vector sin problemas. He usado el siguiente patrón:
fuente
[expr.delete]
en el FCD de C ++ 0x: <cita> En la primera alternativa (eliminar objeto), si el tipo estático del objeto a eliminar es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del objeto que se va a eliminar y el tipo estático tendrá un destructor virtual o el comportamiento no está definido. </quote>Si sigue un buen estilo de C ++, la ausencia de función virtual no es el problema, sino el corte (consulte https://stackoverflow.com/a/14461532/877329 )
¿Por qué la ausencia de funciones virtuales no es el problema? Porque una función no debe intentar con
delete
ningún puntero que reciba, ya que no es propietaria de ella. Por lo tanto, si se siguen estrictas políticas de propiedad, los destructores virtuales no deberían ser necesarios. Por ejemplo, esto siempre es incorrecto (con o sin destructor virtual):Por el contrario, esto siempre funcionará (con o sin destructor virtual):
Si el objeto es creado por una fábrica, la fábrica también debe devolver un puntero a un eliminador de trabajo, que debe usarse en lugar de
delete
, ya que la fábrica puede usar su propio montón. La persona que llama puede obtenerlo en forma de ashare_ptr
ounique_ptr
. En resumen, nodelete
cualquier cosa que usted no recibió directamente desdenew
.fuente
Sí, es seguro siempre que tenga cuidado de no hacer las cosas que no son seguras ... No creo que haya visto a nadie usar un vector nuevo, por lo que en la práctica probablemente estará bien. Sin embargo, no es el idioma común en c ++ ...
¿Puede dar más información sobre cuáles son los algoritmos?
A veces terminas yendo por un camino con un diseño y luego no puedes ver los otros caminos que podrías haber tomado, el hecho de que afirmas que necesitas vectores con 10 nuevos algoritmos suena campanas de alarma para mí, ¿hay realmente 10 propósitos generales? algoritmos que un vector puede implementar, o ¿está tratando de hacer un objeto que sea a la vez un vector de propósito general Y que contenga funciones específicas de la aplicación?
Ciertamente no estoy diciendo que no debas hacer esto, es solo que con la información que has dado están sonando las alarmas, lo que me hace pensar que tal vez algo está mal con tus abstracciones y que hay una mejor manera de lograr lo que querer.
fuente
También heredé
std::vector
recientemente y me pareció muy útil y hasta ahora no he tenido ningún problema.Mi clase es una clase de matriz dispersa, lo que significa que necesito almacenar mis elementos de matriz en algún lugar, es decir, en un
std::vector
. Mi razón para heredar fue que era un poco flojo para escribir interfaces para todos los métodos y también estoy interconectando la clase a Python a través de SWIG, donde ya hay un buen código de interfaz parastd::vector
. Me resultó mucho más fácil extender este código de interfaz a mi clase en lugar de escribir uno nuevo desde cero.El único problema que veo con el enfoque no es tanto con el destructor no virtual, sino que algunos otros métodos, los cuales me gustaría a la sobrecarga, tales como
push_back()
,resize()
,insert()
etc. herencia privada de hecho podría ser una buena opción.¡Gracias!
fuente
Aquí, permítanme presentarles 2 formas más de hacer lo que quieren. Una es otra forma de envolver
std::vector
, otra es la forma de heredar sin dar a los usuarios la oportunidad de romper nada:std::vector
sin escribir muchos envoltorios de funciones.std::vector
y evitar el problema dtor.fuente
Se garantiza que esta pregunta producirá un agarre de perlas sin aliento, pero de hecho no hay ninguna razón defendible para evitar, o "multiplicar innecesariamente las entidades" para evitar, la derivación de un contenedor estándar. La expresión más simple y corta posible es la más clara y la mejor.
Es necesario ejercer todo el cuidado habitual en torno a cualquier tipo derivado, pero no hay nada de especial en el caso de una base del Estándar. Anular una función de miembro base podría ser complicado, pero eso no sería prudente hacer con una base no virtual, por lo que no hay mucho especial aquí. Si tuviera que agregar un miembro de datos, debería preocuparse por cortar si el miembro tuviera que mantenerse coherente con el contenido de la base, pero de nuevo, eso es lo mismo para cualquier base.
El lugar donde he encontrado que derivar de un contenedor estándar es particularmente útil es agregar un solo constructor que realice precisamente la inicialización necesaria, sin posibilidad de confusión o secuestro por parte de otros constructores. (¡Te estoy mirando, constructores de initialization_list!) Entonces, puedes usar libremente el objeto resultante, cortado - pasarlo por referencia a algo que espera la base, pasar de él a una instancia de la base, ¿qué tienes? No hay casos extremos de los que preocuparse, a menos que le moleste vincular un argumento de plantilla a la clase derivada.
Un lugar donde esta técnica será inmediatamente útil en C ++ 20 es la reserva. Donde podríamos haber escrito
podemos decir
y luego, incluso como miembros de la clase,
(según preferencia) y no es necesario escribir un constructor solo para llamar a reserve () en ellos.
(La razón por la que
reserve_in
, técnicamente, debe esperar a C ++ 20 es que los estándares anteriores no requieren que la capacidad de un vector vacío se conserve en los movimientos. Eso se reconoce como un descuido y se puede esperar que se repare razonablemente como un defecto a tiempo para el '20. También podemos esperar que la solución sea, efectivamente, retrocedida a los Estándares anteriores, porque todas las implementaciones existentes realmente conservan la capacidad en los movimientos; los Estándares simplemente no lo han requerido. la reserva de armas casi siempre es solo una optimización de todos modos).Algunos argumentarían que el caso de
reserve_in
es mejor atendido por una plantilla de función gratuita:Tal alternativa es ciertamente viable, y podría incluso, a veces, ser infinitamente más rápida, debido a * RVO. Pero la elección de la derivación o la función libre debe hacerse por sus propios méritos, y no a partir de una superstición sin fundamento (¡je!) Sobre derivar de componentes estándar. En el ejemplo de uso anterior, solo la segunda forma funcionaría con la función libre; aunque fuera del contexto de la clase, podría escribirse de manera más concisa:
fuente