Desarrollo de clases de interfaz en C ++


Las clases de interfaz son muy utilizadas en programas C ++. Pero, desafortunadamente, a menudo se cometen errores al implementar soluciones basadas en clases de interfaz. El artículo describe cómo diseñar correctamente las clases de interfaz; se consideran varias opciones. El uso de punteros inteligentes se describe en detalle. Se proporciona un ejemplo de la implementación de una clase de excepción y una plantilla de clase de colección basada en clases de interfaz.




Tabla de contenidos




Introduccion


Una clase de interfaz es una clase que no tiene datos y se compone principalmente de funciones puramente virtuales. Esta solución le permite separar completamente la implementación de la interfaz (el cliente usa la clase de interfaz) en otro lugar, se crea una clase derivada en la que se redefinen funciones puramente virtuales y se define la función de fábrica. Los detalles de implementación están completamente ocultos para el cliente. De esta forma, se implementa la verdadera encapsulación, que es imposible con la clase habitual. Puede leer sobre las clases de interfaz de Scott Meyers [Meyers2]. Las clases de interfaz también se denominan clases de protocolo.


El uso de clases de interfaz le permite debilitar las dependencias entre diferentes partes del proyecto, lo que simplifica el desarrollo del equipo y reduce el tiempo de compilación / ensamblaje. Las clases de interfaz facilitan la implementación de soluciones flexibles y dinámicas cuando los módulos se cargan selectivamente en tiempo de ejecución. El uso de las clases de interfaz como una biblioteca de interfaz (API) (SDK) simplifica la solución de problemas de compatibilidad binaria.


Las clases de interfaz se usan ampliamente, con su ayuda implementan la interfaz (API) de bibliotecas (SDK), la interfaz de complementos (complementos) y mucho más. Muchos patrones de Gang of Four [GoF] se implementan naturalmente usando clases de interfaz. Las clases de interfaz incluyen interfaces COM. Pero, desafortunadamente, a menudo se cometen errores al implementar soluciones basadas en clases de interfaz. Intentemos aclarar este problema.



1. Funciones especiales para miembros, creación y eliminación de objetos.


Esta sección describe brevemente una serie de características de C ++ que necesita conocer para comprender completamente las soluciones ofrecidas para las clases de interfaz.



1.1. Funciones especiales para miembros


Si el programador no ha definido las funciones miembro de la clase de la siguiente lista (el constructor predeterminado, el constructor de copia, el operador de asignación de copia, el destructor), entonces el compilador puede hacer esto por él. C ++ 11 agregó un constructor de movimientos y un operador de asignación de movimientos a esta lista. Estas funciones miembro se denominan funciones miembro especiales. Se generan solo si se usan y se cumplen condiciones adicionales específicas para cada función. Llamamos la atención sobre el hecho de que este uso puede resultar bastante oculto (por ejemplo, al implementar la herencia). Si no se puede generar la función requerida, se genera un error. (Con la excepción de las operaciones de reubicación, se reemplazan por operaciones de copia). Las funciones miembro generadas por el compilador son públicas e integrables.


Las funciones miembro especiales no se heredan, si se requiere una función miembro especial en la clase derivada, el compilador siempre intentará generarla; la presencia de la función miembro correspondiente definida en la clase base por el programador no afecta esto.


El programador puede prohibir la generación de funciones miembro especiales, en C ++ 11 es necesario usar la construcción "=delete" al declarar, en C ++ 98 declara la función miembro correspondiente privada y no define. En la herencia de clases, la prohibición de generar una función miembro especial realizada en la clase base se aplica a todas las clases derivadas.


Si el programador se siente cómodo con las funciones miembro generadas por el compilador, entonces en C ++ 11 puede indicar esto explícitamente, y no simplemente descartar la declaración. Para hacer esto, debe usar la construcción "=default" al declarar, mientras que el código se lee mejor y aparecen características adicionales relacionadas con la administración del nivel de acceso.


Los detalles sobre funciones especiales para miembros se pueden encontrar en [Meyers3].



1.2. Crear y eliminar objetos: detalles básicos


Crear y eliminar objetos utilizando los operadores new/delete es una operación típica de dos en uno. Al llamar a new , la memoria se asigna primero para el objeto. Si la selección es exitosa, se llama al constructor. Si el constructor produce una excepción, la memoria asignada se libera. Cuando se llama al operador de delete , todo sucede en el orden inverso: primero, se llama al destructor, luego se libera la memoria. El destructor no debe lanzar excepciones.


Si el new operador se usa para crear una matriz de objetos, la memoria se asigna primero a toda la matriz. Si la selección es exitosa, se llama al constructor predeterminado para cada elemento de la matriz comenzando desde cero. Si algún constructor arroja una excepción, entonces, para todos los elementos creados de la matriz, se llama al destructor en el orden inverso de la llamada del constructor, luego se libera la memoria asignada. Para eliminar una matriz, debe llamar al operador delete[] (llamado operador de delete para las matrices), y para todos los elementos de la matriz, se llama al destructor en el orden inverso del constructor, luego se libera la memoria asignada.


Atencion Debe llamar a la forma correcta del operador de delete , dependiendo de si se elimina un solo objeto o matriz. Esta regla debe observarse estrictamente, de lo contrario, puede obtener un comportamiento indefinido, es decir, puede suceder cualquier cosa: pérdidas de memoria, bloqueo, etc. Ver [Meyers2] para más detalles.


Las funciones de asignación de memoria estándar std::bad_alloc satisfacen la solicitud y std::bad_alloc una excepción de tipo std::bad_alloc .


Es seguro aplicar cualquier forma del operador de delete a un puntero nulo.


En la descripción anterior, es necesaria una aclaración. Para los llamados tipos triviales (tipos incorporados, estructuras de estilo C), el constructor no puede ser llamado, y el destructor no hace nada en ningún caso. Ver también la sección 1.6.



1.3. Nivel de acceso al destructor


Cuando el operador de delete se aplica a un puntero a una clase, el destructor de esa clase debe estar disponible en el punto de llamada de delete . (Hay una excepción a esta regla, discutida en la Sección 1.6.) Por lo tanto, al hacer que el destructor sea seguro o cerrado, el programador prohíbe el uso del operador de delete cuando el destructor no está disponible. Recuerde que si no se define un destructor en la clase, el compilador lo hará solo, y este destructor estará abierto (consulte la sección 1.1).



1.4. Crear y eliminar en un módulo


Si el new operador creó un objeto, entonces el operador de delete debe estar en el mismo módulo para delete . Hablando en sentido figurado, "ponlo donde lo llevaste". Esta regla es bien conocida; ver, por ejemplo, [Sutter / Alexandrescu]. Si se viola esta regla, puede producirse un "desajuste" de las funciones de asignación y liberación de memoria, lo que, por regla general, conduce a una finalización anormal del programa.



1.5. Deleción polimórfica


Si está diseñando una jerarquía polimórfica de clases cuyas instancias se eliminan utilizando el operador de delete , entonces debe haber un destructor virtual abierto en la clase base, esto asegura que se llama al destructor del tipo real del objeto cuando el operador de delete se aplica al puntero a la clase base. Si se viola esta regla, puede ocurrir una llamada al destructor de la clase base, lo que puede conducir a una pérdida de recursos.



1.6. Eliminar cuando la declaración de clase está incompleta


La omnivorosidad del operador de delete puede crear ciertos problemas; se puede aplicar a un puntero de tipo void* o a un puntero a una clase que tiene una declaración incompleta (preventiva). En este caso, no se produce un error, solo se omite la llamada al destructor, solo se llama a la función para liberar la memoria. Considere un ejemplo:


 class X; //   X* CreateX(); void Foo() {    X* p = CreateX();    delete p; } 

Este código se compila incluso si una declaración de clase X completa no está disponible en el dial dial de delete . Es cierto que al compilar (Visual Studio) se emite una advertencia:


warning C4150: deletion of pointer to incomplete type 'X'; no destructor called


Si hay una implementación de X y CreateX() , entonces el código se CreateX() , si CreateX() devuelve un puntero al objeto creado por el new operador, entonces la llamada Foo() ejecuta con éxito, no se llama al destructor. Está claro que esto puede conducir a una fuga de recursos, por lo que una vez más sobre la necesidad de tener cuidado con las advertencias.


Esta situación no es descabellada, puede surgir fácilmente cuando se utilizan clases como punteros inteligentes o clases de descriptores. Scott Meyers trata este problema en [Meyers3].



2. Funciones puramente virtuales y clases abstractas.


El concepto de clases de interfaz se basa en conceptos de C ++ como funciones virtuales puras y clases abstractas.



2.1. Funciones virtuales puras


Una función virtual declarada usando la construcción "=0" se llama virtual pura.


 class X { // ...    virtual void Foo() = 0; }; 

A diferencia de una función virtual normal, no se puede definir una función puramente virtual (con la excepción del destructor, consulte la sección 2.3), pero debe redefinirse en una de las clases derivadas.


Se pueden definir funciones puramente virtuales. Emblem Sutter ofrece varios usos útiles para esta función [Obturador].



2.2. Clases abstractas


Una clase abstracta es una clase que tiene al menos una función puramente virtual. Una clase que se deriva de una clase abstracta y no anula al menos una función puramente virtual también será abstracta. El estándar C ++ prohíbe crear instancias de una clase abstracta; solo puede crear instancias de derivados de clases no abstractas. Por lo tanto, se crea una clase abstracta para ser utilizada como una clase base. En consecuencia, si un constructor se define en una clase abstracta, entonces no tiene sentido hacerlo abierto, debe protegerse.



2.3. Destructor virtual puro


En algunos casos, es aconsejable hacer un destructor virtual puro. Pero esta solución tiene dos características.


  1. Se debe definir un destructor puramente virtual. (La definición predeterminada generalmente se usa, es decir, usando la construcción "=default" ). El destructor de clase derivado llama a los destructores de clase base a lo largo de toda la cadena de herencia y, por lo tanto, se garantiza que la cola llegue a la raíz, un destructor puramente virtual.
  2. Si el programador no ha redefinido un destructor virtual puro en la clase derivada, el compilador lo hará por él (consulte la sección 1.1). Por lo tanto, una clase derivada de una clase abstracta con un destructor puramente virtual puede perder su abstracción sin anular explícitamente el destructor.

Un ejemplo del uso de un destructor virtual puro se puede encontrar en la sección 4.4.



3. Clases de interfaz


Una clase de interfaz es una clase abstracta que no tiene datos y se compone principalmente de funciones puramente virtuales. Dicha clase puede tener funciones virtuales ordinarias (no puramente virtuales), por ejemplo, un destructor. También puede haber funciones miembro estáticas, como las funciones de fábrica.



3.1. Implementaciones


Una implementación de una clase de interfaz se llamará una clase derivada en la que se redefinen funciones puramente virtuales. Puede haber varias implementaciones de la misma clase de interfaz, y son posibles dos esquemas: horizontal, cuando varias clases diferentes heredan la misma clase de interfaz, y vertical, cuando la clase de interfaz es la raíz de la jerarquía polimórfica. Por supuesto, puede haber híbridos.


El punto clave del concepto de clases de interfaz es la separación completa de la interfaz de la implementación: el cliente solo trabaja con la clase de interfaz, la implementación no está disponible.



3.2. Creación de objetos


La inaccesibilidad de la clase de implementación causa ciertos problemas al crear objetos. El cliente debe crear una instancia de la clase de implementación y obtener un puntero a la clase de interfaz a través de la cual se accederá al objeto. Como la clase de implementación no está disponible, no puede usar el constructor, por lo que se usa la función de fábrica, que se define en el lado de la implementación. Esta función generalmente crea un objeto usando el new operador y devuelve un puntero al objeto creado, convertido en un puntero a una clase de interfaz. Una función de fábrica puede ser un miembro estático de una clase de interfaz, pero no es necesario, por ejemplo, puede ser miembro de una clase de fábrica especial (que, a su vez, puede ser una clase de interfaz) o una función libre. Una función de fábrica puede devolver no un puntero sin formato a una clase de interfaz, sino uno inteligente. Esta opción se discute en las secciones 3.3.4 y 4.3.2.



3.3. Eliminar objeto


Eliminar un objeto es una operación extremadamente crítica. Un error da como resultado una pérdida de memoria o una eliminación doble, que generalmente conduce a un bloqueo del programa. A continuación, este problema se considera lo más detallado posible, y se presta mucha atención a la prevención de acciones erróneas de los clientes.


Hay cuatro opciones principales:


  1. Usando el operador de delete .
  2. Usando una función virtual especial.
  3. Usando una función externa.
  4. Eliminación automática mediante puntero inteligente.


3.3.1 Usar el operador de delete


Para hacer esto, debe tener un destructor virtual abierto en la clase de interfaz. En este caso, el operador de delete , que solicita un puntero a una clase de interfaz en el lado del cliente, proporciona una llamada al destructor de la clase de implementación. Esta opción puede funcionar, pero es difícil reconocerla como exitosa. Recibimos llamadas de los operadores new y delete en diferentes lados de la "barrera", new en el lado de implementación, delete en el lado del cliente. Y si la implementación de la clase de interfaz se realiza en un módulo separado (que es algo bastante común), obtenemos una violación de la regla de la sección 1.4.



3.3.2. Usando una función virtual especial


Más progresiva es otra opción: la clase de interfaz debe tener una función virtual especial que elimine el objeto. Dicha función, al final, se reduce a llamar a delete this , pero esto ya está sucediendo en el lado de la implementación. Dicha función se puede invocar de diferentes maneras, por ejemplo Delete() , pero también se usan otras opciones: Release() , Destroy() , Dispose() , Free() , Close() , etc. Además de seguir la regla en la sección 1.4, esta opción tiene varias ventajas adicionales.


  1. Le permite utilizar funciones personalizadas de asignación / desasignación de memoria para la clase de implementación.
  2. Le permite implementar un esquema más complejo para controlar la vida útil del objeto de implementación, por ejemplo, utilizando un contador de referencia.

En esta realización, se puede compilar e incluso realizar un intento de eliminar un objeto utilizando el operador de delete , pero esto es un error. Para evitarlo en la clase de interfaz, es suficiente tener un destructor protegido vacío o puramente virtual (ver sección 1.3). Tenga en cuenta que el uso del operador de delete puede estar bastante enmascarado, por ejemplo, los punteros inteligentes estándar utilizan el operador de eliminación para eliminar un objeto de forma predeterminada y el código correspondiente está profundamente oculto en su implementación. Un destructor protegido le permite detectar todos esos intentos en la etapa de compilación.



3.3.3 Usar una función externa


Esta opción puede atraer una cierta simetría de procedimientos para crear y eliminar un objeto, pero en realidad no tiene ventajas sobre la versión anterior, pero hay muchos problemas adicionales. Esta opción no se recomienda para su uso y no se considera en el futuro.



3.3.4 Eliminación automática mediante puntero inteligente


En este caso, la función de fábrica no devuelve un puntero sin formato a una clase de interfaz, sino un puntero inteligente correspondiente. Este puntero inteligente se crea en el lado de implementación y encapsula el objeto de eliminación, que elimina automáticamente el objeto de implementación cuando el puntero inteligente (o su última copia) queda fuera del alcance en el lado del cliente. En este caso, es posible que no se requiera una función virtual especial para eliminar el objeto de implementación, pero aún se necesita un destructor protegido, es necesario para evitar el uso erróneo del operador de delete . (Es cierto que debe tenerse en cuenta que la probabilidad de tal error se reduce notablemente). Esta opción se analiza con más detalle en la Sección 4.3.2.



3.4. Otras opciones para administrar la vida útil de una instancia de una clase de implementación


En algunos casos, el cliente puede recibir un puntero a la clase de interfaz, pero no ser el propietario. La gestión de la vida útil del objeto de implementación está completamente del lado de la implementación. Por ejemplo, un objeto puede ser un objeto singleton estático (esta solución es típica para las fábricas). Otro ejemplo está relacionado con la interacción bidireccional, ver sección 3.7. El cliente no debe eliminar dicho objeto, pero se necesita un destructor protegido para dicha clase de interfaz, es necesario evitar el uso erróneo del operador de delete .



3.5. Copiar semántica


Para una clase de interfaz, no es posible crear una copia del objeto de implementación utilizando el constructor de copia, por lo que si se requiere copiar, la clase debe tener una función virtual que cree una copia del objeto de implementación y devuelva un puntero a la clase de interfaz. Dicha función a menudo se denomina constructor virtual, y su nombre tradicional es Clone() o Duplicate() .


El uso del operador de asignación de copias no está prohibido, pero no puede considerarse una buena idea. El operador de asignación de copia siempre está emparejado; debe estar emparejado con el constructor de copia. El operador generado por el compilador predeterminado no tiene sentido; no hace nada. Teóricamente, puede declarar un operador de asignación puramente virtual, seguido de anulación, pero la asignación virtual no es una práctica recomendada; los detalles se pueden encontrar en [Meyers1]. Además, la asignación parece poco natural: el acceso a los objetos de la clase de implementación generalmente se realiza a través de un puntero a la clase de interfaz, por lo que la asignación se verá así:


 * = *; 

El operador de asignación está mejor prohibido, y si es necesario, dicha semántica tiene en la clase de interfaz la función virtual correspondiente.


Hay dos formas de prohibir la asignación.


  1. Declare el operador de asignación eliminado ( =delete ). Si las clases de interfaz forman una jerarquía, esto es suficiente para hacerlo en la clase base. La desventaja de este método es que afecta la clase de implementación, la prohibición también se aplica a ella.
  2. Declare una declaración de asignación protegida con una definición predeterminada ( =default ). Esto no afecta a la clase de implementación, pero en el caso de una jerarquía de clases de interfaz, dicho anuncio debe hacerse en cada clase.


3.6. Constructor de clase de interfaz


A menudo, el constructor de una clase de interfaz no se declara. En este caso, el compilador genera el constructor predeterminado necesario para implementar la herencia (ver sección 1.1). Este constructor está abierto, aunque lo suficiente como para ser seguro. Si en la clase de interfaz el constructor de copia se declara eliminado ( =delete ), la generación por el compilador del constructor se suprime por defecto, y dicho constructor debe declararse explícitamente. Es natural hacerlo seguro con una definición predeterminada ( =default ). En principio, la declaración de tal constructor protegido siempre se puede hacer. Un ejemplo está en la sección 4.4.



3.7. Interacción bidireccional


Las clases de interfaz son convenientes para usar la comunicación bidireccional. Si se puede acceder a algún módulo a través de las clases de interfaz, el cliente también puede crear implementaciones de algunas clases de interfaz y pasarles punteros en el módulo. A través de estos punteros, el módulo puede recibir servicios del cliente y también transmitir datos o notificaciones al cliente.



3.8. Punteros inteligentes


Dado que el acceso a los objetos de la clase de implementación generalmente se realiza a través de un puntero, es natural utilizar punteros inteligentes para controlar su vida útil. Pero debe tenerse en cuenta que si se usa la segunda opción para eliminar objetos, entonces con el puntero inteligente estándar es necesario transferir un eliminador de usuario (tipo) o una instancia de este tipo. Si esto no se hace, el puntero inteligente utilizará el operador de eliminación para eliminar el objeto, y el código simplemente no se compilará (gracias al destructor protegido). Los punteros inteligentes estándar (incluido el uso de removedores personalizados) se analizan en detalle en [Josuttis], [Meyers3]. Un ejemplo de uso de un removedor personalizado se puede encontrar en la sección 4.3.1.


, , , .



3.9. -


- const. , , -, .



3.10. COM-


COM- , , COM — , COM- , C, , . COM- C++ , COM.



3.11.


(API) (SDK). . -, -, . , (Windows DLL), : -. . , , . LoadLibrary() , -, .



4.



4.1.


, .


 class IBase { protected:    virtual ~IBase() = default; //   public:    virtual void Delete() = 0; //      IBase& operator=(const IBase&) = delete; //   }; 

.


 class IActivatable : public IBase { protected:    ~IActivatable() = default; //   public:    virtual void Activate(bool activate) = 0;    static IActivatable* CreateInstance(); // - }; 

, , . , IBase . , (. 1.3). , .



4.2.


 class Activator : private IActivatable { // ... private:    Activator(); protected:    ~Activator(); public:    void Delete() override;    void Activate(bool activate) override;    friend IActivatable* IActivatable::CreateInstance(); }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Delete() { delete this; } void Activator::Activate(bool activate) {/* ... */} IActivatable* IActivatable::CreateInstance() {    return static_cast<IActivatable*>(new Activator()); } 

, , , - , .



4.3.



4.3.1.


. - ( IBase ):


 struct BaseDeleter {    void operator()(IBase* p) const { p->Delete(); } }; 

std::unique_ptr<> - :


 template <class I> // IIBase using UniquePtr = std::unique_ptr<I, BaseDeleter>; 

, , - , UniquePtr .


-:


 template <class I> // I —  - CreateInstance() UniquePtr<I> CreateInstance() {    return UniquePtr<I>(I::CreateInstance()); } 

:


 template <class I> // IIBase UniquePtr<I> ToPtr(I* p) {    return UniquePtr<I>(p); } 

std::shared_ptr<> std::unique_ptr<> , , std::shared_ptr<> . Activator .


 auto un1 = CreateInstance<IActivatable>(); un1->Activate(true); auto un2 = ToPtr(IActivatable::CreateInstance()); un2->Activate(true); std::shared_ptr<IActivatable> sh = CreateInstance<IActivatable>(); sh->Activate(true); 

( — -):


 std::shared_ptr<IActivatable> sh2(IActivatable::CreateInstance()); 

std::make_shared<>() , ( ).


: , . : , - . 4.4.



4.3.2.


. -. std::shared_ptr<> , , ( ). std::shared_ptr<> ( ) - , delete . std::shared_ptr<> - ( ) - . .


 #include <memory> class IActivatable; using ActPtr = std::shared_ptr<IActivatable>; //   class IActivatable { protected:    virtual ~IActivatable() = default; //      IActivatable& operator=(const IActivatable&) = default; //   public:    virtual void Activate(bool activate) = 0;    static ActPtr CreateInstance(); // - }; //   class Activator : public IActivatable { // ... public:    Activator();  //      ~Activator(); //      void Activate(bool activate) override; }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Activate(bool activate) {/* ... */} ActPtr IActivatable::CreateInstance() {    return ActPtr(new Activator()); } 

- std::make_shared<>() :


 ActPtr IActivatable::CreateInstance() {    return std::make_shared<Activator>(); } 

std::unique_ptr<> , , - , .



4.4.


C# Java C++ «», . . IBase .


 class IBase { protected:    IBase() = default;    virtual ~IBase() = 0; // ,       virtual void Delete(); //   public:    IBase(const IBase&) = delete;            //      IBase& operator=(const IBase&) = delete; //      struct Deleter        // -    {        void operator()(IBase* p) const { p->Delete(); }    };    friend struct IBase::Deleter; }; 

, Delete() , .


 IBase::~IBase() = default; void IBase::Delete() { delete this; } 

IBase . Delete() , . - IBase . Delete() , - . Delete() , . , 4.3.1.



5. ,



5.1


, , , , .


, , IException Exception .


 class IException {    friend class Exception;    virtual IException* Clone() const = 0;    virtual void Delete() = 0; protected:    virtual ~IException() = default; public:    virtual const char* What() const = 0;    virtual int Code() const = 0;    IException& operator=(const IException&) = delete; }; class Exception {    IException* const m_Ptr; public:    Exception(const char* what, int code);    Exception(const Exception& src) : m_Ptr(src.m_Ptr->Clone()) {}    ~Exception() { m_Ptr->Delete(); }    const IException* Ptr() const { return m_Ptr; } }; 

Exception , IException . , throw , . Exception , . - , .


Exception , , .


IException :


 class ExcImpl : IException {    friend class Exception;    const std::string m_What;    const int m_Code;    ExcImpl(const char* what, int code);    ExcImpl(const ExcImpl&) = default;    IException* Clone() const override;    void Delete() override; protected:    ~ExcImpl() = default; public:    const char* What() const override;    int Code() const override; }; ExcImpl::ExcImpl(const char* what, int code)    : m_What(what), m_Code(code) {} IException* ExcImpl::Clone() const { return new ExcImpl(*this); } void ExcImpl::Delete() { delete this; } const char* ExcImpl::What() const { return m_What.c_str(); } int ExcImpl::Code() const { return m_Code; } 

Exception :


 Exception::Exception(const char* what, int code)    : m_Ptr(new ExcImpl(what, code)) {} 

, — .NET — , — , C++/CLI. , , , C++/CLI.



5.2


- :


 template <typename T> class ICollect { protected:    virtual ~ICollect() = default; public:    virtual ICollect<T>* Clone() const = 0;    virtual void Delete() = 0;    virtual bool IsEmpty() const = 0;    virtual int GetCount() const = 0;    virtual T& GetItem(int ind) = 0;    virtual const T& GetItem(int ind) const = 0;    ICollect<T>& operator=(const ICollect<T>&) = delete; }; 

, -, .


 template <typename T> class ICollect; template <typename T> class Iterator; template <typename T> class Contain {    typedef ICollect<T> CollType;    CollType* m_Coll; public:    typedef T value_type;    Contain(CollType* coll);    ~Contain(); //     Contain(const Contain& src);    Contain& operator=(const Contain& src); //     Contain(Contain&& src);    Contain& operator=(Contain&& src);    bool mpty() const;    int size() const;    T& operator[](int ind);    const T& operator[](int ind) const;    Iterator<T> begin();    Iterator<T> end(); }; 

. , . , , , , - begin() end() , . (. [Josuttis]), for . . , , .



6. -


. -, . . , ++. , .NET, Java Pyton. . , , . .NET Framework C++/CLI C++. .



7.


-, .


.


  1. delete .
  2. .
  3. .

.


, delete . , .


- , . , , delete .


.


, , , , .





[GoF]
Gamma E., Helm R., Johnson R., Vlissides J. Métodos de diseño orientado a objetos. Patrones de diseño.: Por. del ingles - San Petersburgo: Peter, 2001.


[Josuttis]
Josattis, Nikolai M. Biblioteca estándar de C ++: Guía de referencia, 2ª ed .: Per. del ingles- M .: LLC "I.D. Williams, 2014.


[Dewhurst]
Dewhurst, Stefan K. Lugares resbaladizos C ++. Cómo evitar problemas al diseñar y compilar sus programas.: Per. del ingles - M .: DMK Press, 2012.


[Meyers1]
, . C++. 35 .: . del ingles — .: , 2000.


[Meyers2]
, . C++. 55 .: . del ingles — .: , 2014.


[Meyers3]
, . C++: 42 C++11 C++14.: . del ingles — .: «.. », 2016.


[Sutter]
, . C++.: . del ingles — : «.. », 2015.


[Sutter/Alexandrescu]
, . , . ++.: . del ingles — .: «.. », 2015.




Source: https://habr.com/ru/post/es427281/


All Articles