Me hizo encontrar a algunas de las preguntas ya ASÍ con título similar – pero cuando leí las respuestas que se centran en diferentes partes de la pregunta que fueron muy específicos (por ejemplo, STL/contenedores).

Podría alguien por favor, muéstrame por qué usted debe utilizar punteros o referencias para la aplicación de polimorfismo? Puedo entender punteros pueden ayudar – pero seguramente sólo hace referencia a diferenciar entre el paso por valor y paso por referencia??

Seguramente tan largo como asignar memoria en el montón – de modo que usted puede tener enlaces dinámicos entonces esto habría sido suficiente – obviamente no.

InformationsquelleAutor user997112 | 2013-03-03

6 Comentarios

  1. 45

    En C++, un objeto siempre tiene un tipo fijo y el tamaño conocido en tiempo de compilación y (si puede y tiene su dirección) existe siempre en una dirección fija para la duración de su vida útil. Estas son las características heredadas de C que contribuyen a hacer de ambos idiomas adecuados, de bajo nivel de la programación de sistemas. (Todo esto está sujeto a el como-si, regla, sin embargo: una conformes compilador es libre de hacer lo que le plazca con el código de tiempo que puede ser demostrado tener ningún efecto detectable en cualquier comportamiento de un conformes programa que está garantizado por la norma.)

    Un virtual función en C++ se define (más o menos, sin necesidad de extrema lenguaje de la abogacía) como la ejecución basado en el tipo de tiempo de ejecución de un objeto; cuando se le llama directamente sobre un objeto siempre será el tipo en tiempo de compilación del objeto, por lo que no hay polimorfismo cuando un virtual función se llama de esta manera.

    Tenga en cuenta que esto no necesariamente tiene que ser el caso: tipos de objeto con virtual funciones son generalmente implementado en C++ con un por-objeto puntero a una tabla de virtual funciones, la cual es única para cada tipo. Si así lo quisieran, un compilador para algunos hipotética variante de C++ podría implementar la asignación de objetos (tales como Base b; b = Derived()) como copiar el contenido del objeto y la virtual puntero a la tabla junto con él, que fácilmente podría funcionar si ambos Base y Derived eran del mismo tamaño. En el caso de que los dos no eran del mismo tamaño, el compilador podría incluso insertar el código que pone en pausa el programa para cualquier cantidad de tiempo con el fin de reorganizar la memoria en el programa y la actualización de todas las posibles referencias a la memoria en una forma que pueda ser demostrado tener ningún efecto detectable en la semántica del programa, terminando el programa si no reordenamiento se puede encontrar: esto sería muy ineficiente, a pesar de, y no se puede garantizar para nunca parar, obviamente no las características deseables para un operador de asignación para tener.

    Así que en lugar de lo anterior, el polimorfismo en C++ se consigue permitiendo que las referencias y los punteros a los objetos de referencia y señalar objetos de su declarado en tiempo de compilación y tipos de cualquiera de los subtipos de la misma. Cuando un virtual se llama a la función a través de una referencia o puntero, y el compilador no puede probar que el objeto al que hace referencia o se señala es de un tipo en tiempo de ejecución con una aplicación conocida de que virtual función, el compilador inserta el código que busca la correcta virtual función para llamar a un tiempo de ejecución. No tiene que ser de esta manera, ya sea: referencias y punteros podría haber sido definidos como no polimórficas (rechazando a la referencia o punto a los subtipos de su declarado tipos) y obligando al programador a venir para arriba con formas alternativas de implementación de polimorfismo. El último es claramente posible ya que se hace todo el tiempo en C, pero en ese punto no hay mucho motivo para tener un nuevo idioma a todos.

    En suma, la semántica de C++ están diseñados de tal manera para permitir que el alto nivel de abstracción y encapsulación de orientado a objetos polimorfismo al tiempo que conserva las características (tales como los de bajo nivel de acceso y la gestión explícita de memoria) que le permiten ser adecuado para el bajo nivel de desarrollo. Usted puede diseñar fácilmente un idioma que había algunos otros semántica, pero no sería de C++ y que tienen diferentes ventajas y desventajas.

    • Así estamos diciendo que el sistema de tiempo de ejecución sólo hará vtable búsquedas, si ve que el objeto fue declarado con un puntero, y que es la forma en que el C++ diseño fue? Por lo tanto polimorfismo sólo funciona con un puntero (o de referencia) porque esa es la única manera de que el tiempo de ejecución hará una vtable de búsqueda y, por tanto, realizar el polimorfismo?
    • No es que el objeto se declara con un puntero, es que usted está llamando a un virtual función a través de un puntero o una referencia. Un objeto es siempre crear una instancia como una completa tipo conocido en tiempo de compilación, si se pone referenciado por un puntero o una referencia posterior no afecta al objeto en sí mismo. (new devuelve un puntero a un objeto en el heap, pero conceptualmente el objeto está todavía allí con su tipo en tiempo de compilación hasta que sea destruido)
    • Usted puede tener variables de objeto en el heap (si son variables de instancia de un asigna el montón de objetos, por ejemplo) o punteros a los objetos asignados en la pila, la semántica es el mismo independientemente.
    • ¿Cuál sería el código para «pila de polimorfismo»?
    • Basta con echar un vistazo en el primer bloque de código en LuchianGrigore la respuesta, Base * b es un puntero polimórfico y pasa a estar apuntando a un objeto de Derived que está en la pila.
    • Esteban, ¿sabes de libros que entran en este tipo de cosas especialmente bien?
    • ¿Existe alguna forma práctica de definir un tipo de base para reservar una cierta cantidad de almacenamiento adicional para los tipos derivados, de tal forma que cualquier tipo derivado, que no añada mucho de cosas de la tienda de todo «en lugar de», y los grandes tipos de la tienda comúnmente utilizado en los campos «en el lugar», junto con un puntero a los datos complementarios?
    • Sí, un libro que detalla este tipo de preguntas sería valioso.
    • c++ es demasiado complicado y poco intuitivo.

  2. 50

    «Seguramente tan largo como asignar memoria en el montón» – donde se asigna la memoria no tiene nada que ver con ella. Es todo acerca de la semántica. Tomemos, por ejemplo:

    Derived d;
    Base* b = &d;

    d está en la pila (memoria automática), pero polimorfismo funcionará en b.

    Si usted no tiene una clase base puntero o una referencia a una clase derivada, el polimorfismo no funciona porque ya no tienen una clase derivada. Tomar

    Base c = Derived();

    La c objeto no es un Derived, pero un Base, porque de rebanar. Así que, técnicamente, el polimorfismo todavía funciona, es sólo que ya no tiene un Derived objeto de que hablar.

    Ahora tomar

    Base* c = new Derived();

    c sólo apunta a algún lugar en la memoria, y que realmente no le importa si eso es en realidad una Base o un Derived, pero la llamada a un virtual método será resuelto de forma dinámica.

    • Pensé rebanar fue a hacer con liskovs principio de sustitución, no de punteros? Derivadas de d = nueva Base() podría causar rebanar porque la Base no es un Derivado…. pero una Derivada es una Base. Entonces, ¿qué es lo que el puntero hace que la convierte en una clase derivada (usted dijo «polimorfismo no funciona porque ya no tienen una clase derivada» – ¿por qué?)
    • cuando se construye Base c = Derived() (que puede o no funcionar, dependiendo de qué funciones se han implementado), c es todavía un Base y sólo tiene el diseño de memoria y variables de instancia de un Base objeto. (Para una cosa, Derived podrían ser más grandes que Base.) Cada una de las variables en C++ tiene una cantidad específica de almacenamiento asignado a él en tiempo de compilación basado en su tipo (que se puede consultar mediante el sizeof operador) y no se puede simplemente cambiar a otro tipo con otro tamaño.
    • Técnicamente, usted está en la derecha, si Base y Derived eran del mismo tamaño, sería posible hacer una modificación de la virtual puntero a la tabla de c en lugar de referirse a la tabla virtual de Derived, pero en general no es una cosa segura para hacer y no es compatible directamente con C++. En C++, un objeto, una vez que está totalmente construido, sigue siendo un tipo fijo hasta que se destruye.
    • No, lo siento, yo no estaba tratando de decir que yo tenía razón – sólo estoy tratando de entender lo que es el puntero permite para el polimorfismo? Un puntero es una dirección de memoria? Seguramente sin un puntero todavía tenemos un objeto que tiene una dirección de memoria?
    • Pero un puntero puede cambiar su valor, de modo que contiene la dirección de un objeto diferente, posiblemente de una clase diferente, con un tamaño diferente. Un objeto sin embargo, no se puede cambiar a ser un objeto diferente. No puede cambiar su propia dirección.
    • el objeto existe en una dirección y tiene un tipo fijo y el tamaño conocido en tiempo de compilación que no puede ser cambiado dentro de la semántica de C++. usted no puede cambiar el tipo de un objeto en tiempo de ejecución porque se puede cambiar el tamaño del objeto y solo se tiene el espacio asignado a la misma (ya sea en la pila de la pila) cuando construcfted. técnicamente, puede hacerlo en su lugar, si los tamaños de la memoria y los diseños eran las mismas, pero C++ semántica no permiten esto.
    • él tiene un punto, no hay nada inherentemente imposible sobre el cambio de un tipo en tiempo de ejecución, si los tamaños y los diseños son compatibles (sólo cambia el vptr manualmente)
    • Así que, básicamente, con un puntero que va a cambiar la dirección a la que apunta desde el tipo estático al dinámico de tipo en tiempo de ejecución? Por lo tanto, si nosotros no tenemos un puntero aún estaríamos señalando el tipo estático (la clase base) y nunca ser capaz de ganar el acceso a los derivados del objeto de funciones virtuales?
    • cuando se llama a una función virtual en cualquier objeto (directamente o a través de una referencia o puntero), siempre basado en el tiempo de ejecución (dinámico) tipo de objeto; sin embargo, C++ semántica garantiza que cada variable de objeto (no un puntero o referencia) en un bien formado programa siempre tiene el tipo fue declarado como en tiempo de compilación (esta es una decisión de diseño). Sin embargo, un puntero o una referencia a un tipo se le permite apuntar a cualquier objeto del tipo declarado o cualquier subtipo (esto también es una decisión de diseño, es posible que podría haber hecho punteros y/o referencias no polimórficas)…
    • …así que cuando se llama a una función a través de un puntero o referencia, el compilador inserta el código que comprueba el real de tipo en tiempo de ejecución (más o menos) y se ejecuta la función correcta. Esto no sucede nunca con una variable de objeto debido a una variable de objeto no es nunca debe cambiar su tipo. Esta es la forma en que el modelo de objetos de obras y es muy efectivo.
    • si pudieras poner a todos que, como una respuesta que yo iba a aceptar – porque eso es lo que yo quería saber.
    • seguro

  3. 12

    Me pareció muy útil para entender que un constructor de copia se invoca a la hora de asignar como este:

    class Base { };    
    class Derived : public Base { };
    
    Derived x; /* Derived type object created */ 
    Base y = x; /* Copy is made (using Base's copy constructor), so y really is of type Base. Copy can cause "slicing" btw. */ 

    Puesto que y es un objeto de la clase Base, en lugar de la original, las funciones de llamada en esta son la Base de las funciones.

  4. 4

    Considerar little endian arquitecturas: los valores son almacenados bajo orden de bytes de la primera. Así que, dado cualquier entero sin signo, los valores de 0 a 255 se almacenan en el primer byte de valor. Accede a la baja de los 8-bits de cualquier valor, simplemente requiere un puntero a la dirección.

    Así podríamos implementar uint8 como una clase. Sabemos que una instancia de uint8 es … un byte. Si que se derivan de él y producir uint16, uint32, etc, la interfaz sigue siendo el mismo para los fines de la abstracción, pero el cambio más importante es el tamaño de las instancias concretas del objeto.

    Por supuesto, si hemos implementado uint8 y char, los tamaños pueden ser el mismo, del mismo modo sint8.

    Sin embargo, operator= de uint8 y uint16 se va a mover diferentes cantidades de datos.

    Con el fin de crear una función Polimórfica debemos ser capaces de:

    un/a recibir el argumento por valor mediante la copia de los datos en una nueva ubicación de la talla correcta y el diseño,
    b/tomar un puntero a la ubicación del objeto,
    c/tomar una referencia a la instancia del objeto,

    Podemos utilizar plantillas para lograr un polimorfismo puede trabajo sin punteros y referencias, pero si no estamos contando las plantillas, a continuación, vamos a considerar lo que ocurre si ponemos en práctica uint128 y se pasa a una función que espera uint8? Respuesta: 8 bits se copian en lugar de 128.

    Así que lo que si hemos hecho nuestro polimórficos función aceptar uint128 y la pasamos a un uint8. Si nuestro uint8 estábamos copiando lamentablemente no se encuentra, nuestra función intento de copia de 128 bytes de los cuales 127 fueron fuera de nuestra memoria accesible -> accidente.

    Considerar el siguiente:

    class A { int x; };
    A fn(A a)
    {
        return a;
    }
    
    class B : public A {
        uint64_t a, b, c;
        B(int x_, uint64_t a_, uint64_t b_, uint64_t c_)
        : A(x_), a(a_), b(b_), c(c_) {}
    };
    
    B b1 { 10, 1, 2, 3 };
    B b2 = fn(b1);
    //b2.x == 10, but a, b and c?

    En el momento fn fue compilado, no había conocimiento de B. Sin embargo, B se deriva de A así polimorfismo debe permitir a las que podemos llamar fn con un B. Sin embargo, la objeto devuelve debería ser un A que comprende un solo int.

    Si se pasa una instancia de B a esta función, lo que nos de la espalda debe ser sólo un { int x; } sin a, b, c.

    Esto es «cortar».

    Incluso con punteros y referencias no podemos evitar esto de forma gratuita. Considerar:

    std::vector<A*> vec;

    Los elementos de este vector podría ser punteros a A o algo derivado de A. El lenguaje en general se soluciona esto mediante el uso de la «vtable», una pequeña adición a la instancia del objeto que identifica el tipo y proporciona punteros de función para las funciones virtuales. Usted puede pensar en él como algo como:

    template<class T>
    struct PolymorphicObject {
        T::vtable* __vtptr;
        T __instance;
    };

    Lugar de cada objeto tiene su propia y distinta de vtable, las clases tienen ellos, y las instancias de objetos simplemente seleccione las correspondientes vtable.

    El problema ahora no es rebanar pero la corrección del tipo:

    struct A { virtual const char* fn() { return "A"; } };
    struct B : public A { virtual const char* fn() { return "B"; } };
    
    #include <iostream>
    #include <cstring>
    
    int main()
    {
        A* a = new A();
        B* b = new B();
        memcpy(a, b, sizeof(A));
        std::cout << "sizeof A = " << sizeof(A)
            << " a->fn(): " << a->fn() << '\n';
    }          

    http://ideone.com/G62Cn0

    sizeof A = 4 a->fn(): B

    Lo que debes hacer es utilizar a->operator=(b)

    http://ideone.com/Vym3Lp

    pero de nuevo, esto es copiar Una a Una y así rebanar iba a ocurrir:

    struct A { int i; A(int i_) : i(i_) {} virtual const char* fn() { return "A"; } };
    struct B : public A {
        int j;
        B(int i_) : A(i_), j(i_ + 10) {}
        virtual const char* fn() { return "B"; }
    };
    
    #include <iostream>
    #include <cstring>
    
    int main()
    {
        A* a = new A(1);
        B* b = new B(2);
        *a = *b; //aka a->operator=(static_cast<A*>(*b));
        std::cout << "sizeof A = " << sizeof(A)
            << ", a->i = " << a->i << ", a->fn(): " << a->fn() << '\n';
    }       

    http://ideone.com/DHGwun

    (i es copiado, pero B j se pierde)

    La conclusión aquí es que los punteros o referencias son necesarios porque la instancia original lleva membresía información con lo que la copia puede interactuar con.

    Pero también, que el polimorfismo no está perfectamente resuelto en C++ y uno debe ser consciente de su obligación de proporcionar a los/bloque de acciones que puedan producir en rodajas.

  5. 1

    Necesita punteros de referencia o debido a que por el tipo de polimorfismo usted está interesado en (*), se necesita que el tipo dinámico pueden ser diferentes de las de tipo estático, es decir, que el verdadero tipo del objeto es diferente del tipo declarado. En C++ que sólo ocurre con los punteros o referencias.


    (*) Genericity, el tipo de polimorfismo proporcionada por las plantillas, no necesita punteros ni referencias.

    • No me refiero a la división paja – pero estoy tratando de entender (en un nivel bajo, supongo) ¿por qué esta parte de su mensaje es: «En C++ que sucede con punteros o referencias». ¿Por qué es este el caso?
    • debido a que el punto de tener la orientación a objetos, es tener algo estático garantías acerca de la duración de los objetos y de las identidades. si los objetos que podrían cambiar arbitrariamente tipos en tiempo de ejecución después de la construcción, sería mucho más difícil mantener invariantes sobre el estado del programa.
    • el modelo de objetos de C++ no lean esa manera.
  6. 0

    Cuando un objeto se pasa por valor, normalmente se ponen en la pila. Poner algo en la pila requiere el conocimiento de lo grande que es. Cuando el uso de polimorfismo, usted sabe que el objeto entrante implementa un conjunto particular de características, pero por lo general no tienen idea de que el tamaño del objeto (ni debe, necesariamente, que parte de la ventaja). Por lo tanto, no puedes ponerlo en la pila. Sin embargo, usted siempre sabe el tamaño de un puntero.

    Ahora, no todo va a la pila, y hay otras circunstancias atenuantes. En el caso de los métodos virtuales, el puntero hacia el objeto también es un puntero al objeto de la vtable(s), que indican la localización de los métodos. Esto permite que el compilador para encontrar y llamar a las funciones, independientemente de qué objeto se está trabajando con.

    Otra causa es que muy a menudo el objeto se lleva a cabo fuera de la convocatoria de la biblioteca, y asignados de una forma completamente diferente (y posiblemente incompatibles) administrador de memoria. También podría haber miembros que no pueden ser copiados, o podría causar problemas si se han copiado con un administrador diferente. Podría haber efectos secundarios a la copia y todo tipo de otras complicaciones.

    El resultado es que el puntero es el único bit de información sobre el objeto que realmente comprender correctamente, y proporciona suficiente información para calcular dónde están los otros bits que necesita son.

    • No voy a -1 usted, pero la pila vs montón no tiene relevancia aquí, sólo añade a la confusión mencionar que.

Dejar respuesta

Please enter your comment!
Please enter your name here