Desenvolvimento de classes de interface em C ++


As classes de interface são muito usadas em programas C ++. Infelizmente, porém, muitas vezes são cometidos erros na implementação de soluções baseadas em classes de interface. O artigo descreve como projetar corretamente as classes da interface; várias opções são consideradas. O uso de ponteiros inteligentes é descrito em detalhes. Um exemplo da implementação de uma classe de exceção e um modelo de classe de coleção baseado em classes de interface é fornecido.




Sumário




1. Introdução


Uma classe de interface é uma classe que não possui dados e consiste principalmente em funções puramente virtuais. Essa solução permite separar completamente a implementação da interface - o cliente usa a classe de interface - em outro local é criada uma classe derivada, na qual funções puramente virtuais são redefinidas e a função de fábrica é definida. Os detalhes da implementação estão completamente ocultos do cliente. Dessa maneira, o verdadeiro encapsulamento é implementado, o que é impossível com a classe usual. Você pode ler sobre as classes de interface em Scott Meyers [Meyers2]. As classes de interface também são chamadas de classes de protocolo.


O uso de classes de interface permite enfraquecer as dependências entre diferentes partes do projeto, o que simplifica o desenvolvimento da equipe e reduz o tempo de compilação / montagem. As classes de interface facilitam a implementação de soluções flexíveis e dinâmicas quando os módulos são carregados seletivamente em tempo de execução. O uso das classes de interface como uma biblioteca de interface (API) (SDK) simplifica a solução de problemas de compatibilidade binária.


As classes de interface são usadas amplamente, com a ajuda de implementar a interface (API) de bibliotecas (SDK), a interface de plug-ins (plugins) e muito mais. Muitos padrões do Gang of Four [GoF] são naturalmente implementados usando classes de interface. As classes de interface incluem interfaces COM. Infelizmente, porém, muitas vezes são cometidos erros na implementação de soluções baseadas em classes de interface. Vamos tentar esclarecer esse problema.



1. Funções-membro especiais, criando e excluindo objetos


Esta seção descreve brevemente vários recursos do C ++ que você precisa conhecer para entender completamente as soluções oferecidas para as classes de interface.



1.1 Funções especiais para membros


Se o programador não definiu as funções de membro da classe da lista a seguir - construtor padrão, construtor de cópias, operador de atribuição de cópias, destruidor -, o compilador pode fazer isso por ele. O C ++ 11 adicionou um construtor de movimentação e um operador de atribuição de movimentação a esta lista. Essas funções de membro são chamadas de funções de membro especiais. Eles são gerados apenas se forem usados ​​e condições adicionais específicas para cada função são atendidas. Observe que esse uso pode se mostrar bastante oculto (por exemplo, ao implementar herança). Se a função necessária não puder ser gerada, um erro será gerado. (Com exceção das operações de realocação, elas são substituídas por operações de cópia.) As funções de membro geradas pelo compilador são públicas e incorporáveis.


Funções-membro especiais não são herdadas; se uma função-membro especial for necessária na classe derivada, o compilador sempre tentará gerá-la; a presença da função-membro correspondente definida na classe base pelo programador não afeta isso.


O programador pode proibir a geração de funções-membro especiais; em C ++ 11, é necessário usar a construção "=delete" ao declarar; em C ++ 98, declarar a função de membro correspondente privada e não definir. Na herança de classe, a proibição de gerar uma função de membro especial feita na classe base se aplica a todas as classes derivadas.


Se o programador estiver confortável com as funções de membro geradas pelo compilador, no C ++ 11 ele poderá indicar isso explicitamente, e não apenas descartar a declaração. Para fazer isso, você deve usar a construção "=default" ao declarar, enquanto o código é melhor lido e recursos adicionais aparecem relacionados ao gerenciamento do nível de acesso.


Detalhes sobre funções-membro especiais podem ser encontrados em [Meyers3].



1.2 Criando e excluindo objetos - detalhes básicos


Criar e excluir objetos usando os operadores new/delete é uma operação típica de duas em uma. Ao chamar new , a memória é alocada primeiro para o objeto. Se a seleção for bem sucedida, o construtor é chamado. Se o construtor lançar uma exceção, a memória alocada será liberada. Quando o operador de delete é chamado, tudo acontece na ordem inversa: primeiro, o destruidor é chamado e a memória é liberada. O destruidor não deve lançar exceções.


Se o new operador for usado para criar uma matriz de objetos, a memória será alocada primeiro para toda a matriz. Se a seleção for bem-sucedida, o construtor padrão será chamado para cada elemento da matriz começando do zero. Se algum construtor lança uma exceção, para todos os elementos criados da matriz, o destruidor é chamado na ordem inversa da chamada do construtor, a memória alocada é liberada. Para excluir uma matriz, você deve chamar o operador delete[] (chamado operador de delete para matrizes) e, para todos os elementos da matriz, o destruidor é chamado na ordem inversa do construtor e a memória alocada é liberada.


Atenção! Você deve chamar a forma correta do operador de delete , dependendo de um único objeto ou matriz ser excluído. Essa regra deve ser observada rigorosamente, caso contrário, você poderá obter um comportamento indefinido, ou seja, tudo pode acontecer: vazamento de memória, falha etc. Veja [Meyers2] para detalhes.


As funções de alocação de memória padrão std::bad_alloc ao atender à solicitação, std::bad_alloc uma exceção do tipo std::bad_alloc .


É seguro aplicar qualquer forma do operador de delete a um ponteiro nulo.


Na descrição acima, é necessário um esclarecimento. Para os chamados tipos triviais (tipos internos, estruturas no estilo C), o construtor não pode ser chamado e o destruidor não faz nada em nenhum caso. Veja também a seção 1.6.



1.3 Nível de acesso do destruidor


Quando o operador de delete é aplicado a um ponteiro de uma classe, o destruidor dessa classe deve estar disponível no ponto de chamada de delete . (Existe alguma exceção a esta regra, discutida na Seção 1.6.) Assim, ao tornar o destruidor seguro ou fechado, o programador proíbe o uso do operador de delete onde o destruidor não está disponível. Lembre-se de que, se nenhum destruidor estiver definido na classe, o compilador fará isso por conta própria e esse destruidor estará aberto (consulte a seção 1.1).



1.4 Crie e exclua em um módulo


Se o new operador criou um objeto, o operador de delete deve estar no mesmo módulo para delete lo. Figurativamente falando, "coloque-o onde você o levou". Essa regra é bem conhecida, veja, por exemplo, [Sutter / Alexandrescu]. Se essa regra for violada, poderá ocorrer “incompatibilidade” das funções de alocação e liberação de memória, o que, via de regra, leva a um encerramento anormal do programa.



1.5 Deleção polimórfica


Se você estiver projetando uma hierarquia polimórfica de classes cujas instâncias são excluídas usando o operador delete , deve haver um destruidor virtual aberto na classe base, isso garante que o destruidor do tipo real do objeto seja chamado quando o operador de delete for aplicado ao ponteiro da classe base. Se essa regra for violada, poderá ocorrer uma chamada para o destruidor da classe base, o que pode levar a um vazamento de recurso.



1.6 Excluindo quando a declaração de classe está incompleta


A onívora do operador de delete pode criar certos problemas; pode ser aplicada a um ponteiro do tipo void* ou a um ponteiro para uma classe que possui uma declaração incompleta (preemptiva). Nesse caso, um erro não ocorre, apenas a chamada para o destruidor é ignorada, apenas a função para liberar a memória é chamada. Considere um exemplo:


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

Esse código é compilado mesmo se uma declaração completa da classe X não estiver disponível no ponto de discagem de delete . Verdadeiro, ao compilar (Visual Studio) um aviso é emitido:


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


Se houver uma implementação de X e CreateX() , o código será CreateX() ; se CreateX() retornar um ponteiro para o objeto criado pelo new operador, a chamada Foo() executada com êxito, o destruidor não será chamado. É claro que isso pode levar a uma drenagem de recursos, portanto, mais uma vez, sobre a necessidade de ter cuidado com os avisos.


Essa situação não é exagerada, pode surgir facilmente ao usar classes como ponteiros inteligentes ou classes de descritores. Scott Meyers lida com esse problema em [Meyers3].



2. Funções puramente virtuais e classes abstratas


O conceito de classes de interface é baseado em conceitos de C ++, como funções virtuais puras e classes abstratas.



2.1 Funções virtuais puras


Uma função virtual declarada usando a construção "=0" é chamada virtual pura.


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

Diferentemente de uma função virtual regular, uma função puramente virtual não pode ser definida (com exceção do destruidor, consulte a seção 2.3), mas deve ser redefinida em uma das classes derivadas.


Funções puramente virtuais podem ser definidas. O emblema Sutter oferece vários usos úteis para esse recurso [Obturador].



2.2 Classes abstratas


Uma classe abstrata é uma classe que possui pelo menos uma função puramente virtual. Uma classe que é derivada de uma classe abstrata e não substitui pelo menos uma função puramente virtual também será abstrata. O padrão C ++ proíbe a criação de instâncias de uma classe abstrata; você só pode criar instâncias de derivadas de classes não abstratas. Assim, uma classe abstrata é criada para ser usada como classe base. Portanto, se um construtor é definido em uma classe abstrata, não faz sentido abri-lo, ele deve ser protegido.



2.3 Destruidor virtual puro


Em alguns casos, é aconselhável criar um destruidor virtual puro. Mas esta solução tem dois recursos.


  1. Um destruidor puramente virtual deve ser definido. (A definição padrão geralmente é usada, ou seja, usando a construção "=default" .) O destruidor de classe derivado chama destruidores de classe base ao longo de toda a cadeia de herança e, portanto, a fila é garantida para atingir a raiz - um destruidor puramente virtual.
  2. Se o programador não redefiniu um destruidor virtual puro na classe derivada, o compilador fará isso por ele (consulte a seção 1.1). Assim, uma classe derivada de uma classe abstrata com um destruidor puramente virtual pode perder sua abstração sem substituir explicitamente o destruidor.

Um exemplo de uso de um destruidor virtual puro pode ser encontrado na seção 4.4.



3. Classes de interface


Uma classe de interface é uma classe abstrata que não possui dados e consiste principalmente em funções puramente virtuais. Essa classe pode ter funções virtuais comuns (não puramente virtuais), por exemplo, um destruidor. Também pode haver funções membro estáticas, como funções de fábrica.



3.1 Implementações


Uma implementação de uma classe de interface será chamada de classe derivada na qual funções puramente virtuais são redefinidas. Pode haver várias implementações da mesma classe de interface, e dois esquemas são possíveis: horizontal, quando várias classes diferentes herdam a mesma classe de interface, e vertical, quando a classe de interface é a raiz da hierarquia polimórfica. Claro, pode haver híbridos.


O ponto principal do conceito de classe de interface é a separação completa da interface da implementação - o cliente trabalha apenas com a classe de interface, a implementação não está disponível para ela.



3.2 Criação de Objetos


A inacessibilidade da classe de implementação causa certos problemas ao criar objetos. O cliente deve criar uma instância da classe de implementação e obter um ponteiro para a classe da interface através da qual o objeto será acessado. Como a classe de implementação não está disponível, você não pode usar o construtor, portanto, a função de fábrica é usada, definida no lado da implementação. Essa função geralmente cria um objeto usando o new operador e retorna um ponteiro para o objeto criado, convertido em um ponteiro para uma classe de interface. Uma função de fábrica pode ser um membro estático de uma classe de interface, mas não é necessário, por exemplo, pode ser um membro de uma classe de fábrica especial (que, por sua vez, pode ser uma classe de interface) ou uma função livre. Uma função de fábrica pode retornar não um ponteiro bruto para uma classe de interface, mas um inteligente. Esta opção é discutida nas seções 3.3.4 e 4.3.2.



3.3 Excluir objeto


A remoção de um objeto é uma operação extremamente crítica. Um erro resulta em um vazamento de memória ou em uma exclusão dupla, o que geralmente leva a uma falha no programa. Abaixo, esse problema é considerado o mais detalhado possível, com muita atenção sendo prestada à prevenção de ações errôneas do cliente.


Existem quatro opções principais:


  1. Usando o operador de delete .
  2. Usando uma função virtual especial.
  3. Usando uma função externa.
  4. Exclusão automática usando o ponteiro inteligente.


3.3.1 Usando o operador de delete


Para fazer isso, você deve ter um destruidor virtual aberto na classe de interface. Nesse caso, o operador de delete , chamado de um ponteiro para uma classe de interface no lado do cliente, fornece uma chamada ao destruidor da classe de implementação. Essa opção pode funcionar, mas é difícil reconhecê-la como bem-sucedida. Recebemos chamadas dos operadores new e delete em diferentes lados da “barreira”, new no lado da implementação e delete no lado do cliente. E se a implementação da classe de interface for feita em um módulo separado (o que é bastante comum), obteremos uma violação da regra na seção 1.4.



3.3.2 Usando uma função virtual especial


Mais progressiva é outra opção: a classe de interface deve ter uma função virtual especial que remova o objeto. Essa função, no final, se resume a chamar delete this , mas isso já está acontecendo no lado da implementação. Essa função pode ser chamada de maneiras diferentes, por exemplo, Delete() , mas outras opções também são usadas: Release() , Destroy() , Dispose() , Free() , Close() , etc. Além de seguir a regra na seção 1.4, esta opção tem várias vantagens adicionais.


  1. Permite usar funções de alocação / desalocação de memória definidas pelo usuário para a classe de implementação.
  2. Permite implementar um esquema mais complexo para controlar a vida útil do objeto de implementação, por exemplo, usando um contador de referência.

Nesta modalidade, uma tentativa de excluir um objeto usando o operador de delete pode ser compilada e até executada, mas isso é um erro. Para evitá-lo na classe de interface, basta ter um destruidor protegido vazio ou puramente virtual (consulte a seção 1.3). Observe que o uso do operador de delete pode ser bastante mascarado, por exemplo, ponteiros inteligentes padrão usam o operador de exclusão para excluir um objeto por padrão e o código correspondente está profundamente oculto em sua implementação. Um destruidor protegido permite detectar todas essas tentativas no estágio de compilação.



3.3.3 Usando uma função externa


Essa opção pode atrair uma certa simetria de procedimentos para criar e excluir um objeto, mas, na realidade, não possui vantagens em relação à versão anterior, mas há muitos problemas adicionais. Esta opção não é recomendada para uso e não é considerada no futuro.



3.3.4 Exclusão automática usando o ponteiro inteligente


Nesse caso, a função de fábrica não retorna um ponteiro bruto para uma classe de interface, mas um ponteiro inteligente correspondente. Esse ponteiro inteligente é criado no lado da implementação e encapsula o objeto de exclusão, que exclui automaticamente o objeto de implementação quando o ponteiro inteligente (ou sua última cópia) fica fora do escopo no lado do cliente. Nesse caso, uma função virtual especial para excluir o objeto de implementação pode não ser necessária, mas ainda é necessário um destruidor protegido; é necessário evitar o uso incorreto do operador de delete . (É verdade, deve-se notar que a probabilidade de um erro desse tipo é visivelmente reduzida.) Essa opção é discutida em mais detalhes na Seção 4.3.2.



3.4 Outras opções para gerenciar o tempo de vida de uma instância de uma classe de implementação


Em alguns casos, o cliente pode receber um ponteiro para a classe da interface, mas não a possui. O gerenciamento da vida útil do objeto de implementação é totalmente do lado da implementação. Por exemplo, um objeto pode ser um objeto singleton estático (essa solução é típica para fábricas). Outro exemplo está relacionado à interação bidirecional, consulte a seção 3.7. O cliente não deve excluir esse objeto, mas é necessário um destruidor protegido para essa classe de interface; é necessário evitar o uso incorreto do operador de delete .



3.5 Semântica de cópia


Para uma classe de interface, a criação de uma cópia do objeto de implementação usando o construtor de cópia não é possível; portanto, se a cópia for necessária, a classe deverá ter uma função virtual que crie uma cópia do objeto de implementação e retorne um ponteiro para a classe de interface. Essa função é geralmente chamada de construtor virtual e seu nome tradicional é Clone() ou Duplicate() .


O uso do operador de atribuição de cópias não é proibido, mas não pode ser considerado uma boa ideia. O operador de atribuição de cópia está sempre emparelhado; deve ser emparelhado com o construtor de cópias. O operador gerado pelo compilador padrão não faz sentido; não faz nada. É teoricamente possível declarar um operador de atribuição puramente virtual com redefinição subsequente, mas a atribuição virtual não é uma prática recomendada; detalhes podem ser encontrados em [Meyers1]. Além disso, a atribuição parece muito antinatural: o acesso aos objetos da classe de implementação geralmente é feito através de um ponteiro para a classe de interface, portanto, a atribuição terá a seguinte aparência:


 * = *; 

É melhor proibir o operador de atribuição e, se necessário, essa semântica tem na classe de interface a função virtual correspondente.


Existem duas maneiras de proibir a atribuição.


  1. Declare o operador de atribuição excluído ( =delete ). Se as classes de interface formam uma hierarquia, isso é suficiente na classe base. A desvantagem desse método é que ele afeta a classe de implementação, a proibição também se aplica a ele.
  2. Declare uma instrução de atribuição protegida com uma definição padrão ( =default ). Isso não afeta a classe de implementação, mas no caso de uma hierarquia de classes de interface, esse anúncio deve ser feito em cada classe.


3.6 Construtor da classe de interface


Freqüentemente, o construtor de uma classe de interface não é declarado. Nesse caso, o compilador gera o construtor padrão necessário para implementar a herança (consulte a seção 1.1). Este construtor é aberto, embora o suficiente para ser seguro. Se na classe de interface o construtor de cópia for declarado excluído ( =delete ), a geração pelo compilador do construtor será suprimida por padrão, e esse construtor deverá ser declarado explicitamente. É natural torná-lo seguro com uma definição padrão ( =default ). Em princípio, a declaração de um construtor protegido pode sempre ser feita. Um exemplo está na seção 4.4.



3.7 Interação bidirecional


As classes de interface são convenientes para o uso da comunicação bidirecional. Se algum módulo estiver acessível através de classes de interface, o cliente também poderá criar implementações de algumas classes de interface e passar ponteiros para elas no módulo. Por meio desses ponteiros, o módulo pode receber serviços do cliente e também transmitir dados ou notificações ao cliente.



3.8 Ponteiros inteligentes


Como o acesso a objetos da classe de implementação geralmente é feito por meio de um ponteiro, é natural usar ponteiros inteligentes para controlar sua vida útil. Porém, deve-se ter em mente que, se a segunda opção para excluir objetos for usada, com o ponteiro inteligente padrão é necessário transferir um deleter (tipo) ou uma instância desse tipo. Se isso não for feito, o ponteiro inteligente usará o operador delete para excluir o objeto, e o código simplesmente não será compilado (graças ao destruidor protegido). Ponteiros inteligentes padrão (incluindo o uso de removedores personalizados) são discutidos em detalhes em [Josuttis], [Meyers3]. Um exemplo de uso de um removedor personalizado pode ser encontrado na seção 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 design orientado a objetos. Padrões de design.: Por. do inglês - São Petersburgo: Peter, 2001.


[Josuttis]
Josattis, Nikolai M. Biblioteca padrão C ++: Reference Guide, 2a ed.: Per. do inglês - M .: LLC “I.D. Williams, 2014.


[Dewhurst]
Dewhurst, Stefan K. Slippery coloca C ++. Como evitar problemas ao projetar e compilar seus programas. do inglês - M .: DMK Press, 2012.


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


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


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


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


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




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


All Articles