Pergunta

Como faço para configurar uma classe que representa uma interface? Isto é apenas uma classe base abstrata?

Foi útil?

Solução

Para expandir a resposta por bradtgmurray , você pode querer fazer uma exceção à lista de método virtual puro de sua interface, adicionando um destrutor virtual. Isto permite-lhe passar a propriedade ponteiro para um outro partido, sem expor a classe derivada concreto. O destruidor não tem que fazer nada, porque a interface não tem quaisquer membros concretos. Pode parecer contraditório para definir uma função tanto como virtual e em linha, mas confia em mim -. Não é

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Você não tem que incluem um corpo para o processo de destruição virtual - despeja alguns compiladores têm problemas para otimizar uma destructor vazio e você é melhor fora de usar o padrão.

Outras dicas

Faça uma classe com métodos virtuais puros. Use a interface através da criação de uma outra classe que substitui os métodos virtuais.

Um método virtual pura é um método de classe que é definido como virtual e atribuído a 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Toda a razão que você tem um especial tipo de categoria-Interface, além de classes base abstratas em C # / Java é porque C # / Java não suporta herança múltipla.

suportes C ++ herança múltipla, e assim um tipo especial não é necessário. Uma classe base sumário com nenhum dos métodos não-sumário (puro virtuais) é funcionalmente equivalente a uma interface C # / Java.

Não existe o conceito de "interface" per se em C ++. AFAIK, interfaces foram introduzidas pela primeira vez em Java para contornar a falta de herança múltipla. Este conceito acabou por ser bastante útil, e o mesmo efeito pode ser conseguido em C ++ usando uma classe base abstrata.

Uma classe base abstrata é uma classe em que pelo menos uma função de membro (método na linguagem Java) é uma função virtual pura declarado usando a seguinte sintaxe:

class A
{
  virtual void foo() = 0;
};

Uma classe base abstrata não pode ser instanciado, i. e. você não pode declarar um objeto da classe A. Você pode apenas classes derivar de uma, mas de qualquer classe derivada que não fornece uma implementação de foo() também serão abstrato. A fim de parar de ser abstrato, uma classe derivada deve fornecer implementações para todas as funções virtuais puras ela herda.

Note que uma classe base abstrata pode ser mais do que uma interface, porque pode conter membros de dados e funções membro que não são puro virtual. Um equivalente de uma interface seria uma classe base abstrata, sem quaisquer dados com funções virtuais única puros.

E, como Mark Ransom apontou, uma classe base abstrata deve fornecer um destrutor virtual, assim como qualquer classe base, para essa matéria.

Até eu poderia testar, é muito importante para adicionar o destruidor virtual. Eu estou usando objetos criados com new e destruídas com delete.

Se você não adicionar o destruidor virtual na interface, em seguida, o destruidor da classe herdada não é chamado.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

Se você executar o código anterior sem virtual ~IBase() {};, você vai ver que o Tester::~Tester() destructor nunca é chamado.

A minha resposta é basicamente o mesmo que os outros, mas eu acho que existem duas outras coisas importantes a fazer:

  1. Declare um destrutor virtual na sua interface ou fazer um não-virtual protegido para evitar comportamentos indefinidos Se alguém tentar excluir um objeto do tipo IDemo.

  2. Use herança virtual para evitar problemas whith de herança múltipla. (Há mais frequentemente herança múltipla quando usamos interfaces.)

E como outras respostas:

  • Faça uma classe com métodos virtuais puros.
  • Use a interface através da criação de uma outra classe que substitui os métodos virtuais.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    ou

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    E

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    

Em C ++ 11 você pode facilmente evitar herança por completo:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

Neste caso, uma interface tem semântica de referência, ou seja, você tem que ter certeza de que o objeto sobrevive a interface (também é possível fazer interfaces com valores semânticos).

Estes tipos de interfaces têm seus prós e contras:

Finalmente, a herança é a raiz de todo o mal em design de software complexo. Em Valor Semântica e de Sean Parent conceitos baseados-Polimorfismo (altamente recomendado melhores versões, desta técnica são explicados lá) o seguinte caso é estudado:

Say Tenho uma aplicação em que eu lidar com minhas formas polymorphically usando a interface MyShape:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

Em seu aplicativo, você fazer o mesmo com diferentes formas usando a interface YourShape:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

Agora, digamos que você deseja usar algumas das formas que eu desenvolvi na sua aplicação. Conceitualmente, nossas formas têm a mesma interface, mas para fazer meus formas trabalhar em seu aplicativo que você precisa para estender as minhas formas como se segue:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

Em primeiro lugar, modificando minhas formas pode não ser possível a todos. Além disso, várias ligações de herança da estrada para código espaguete (imagine um terceiro projeto vem em que é usando a interface TheirShape ... o que acontece se eles também chamam sua função empate my_draw?).

Update: Há um par de novas referências sobre polimorfismo com base não-herança:

Todas as boas respostas acima. Uma coisa extra que você deve ter em mente - você também pode ter um destrutor virtual pura. A única diferença é que você ainda precisa implementá-lo.

Confuso?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

A principal razão que você gostaria de fazer isso é se você deseja fornecer métodos de interface, como eu tenho, mas torná-los substituindo opcional.

Para tornar a classe uma classe de interface exige um método virtual puro, mas todos os seus métodos virtuais têm implementações padrão, então o único método para a esquerda para fazer puro virtual é o destruidor.

reimplementar um destruidor na classe derivada não é grande coisa em tudo -. Eu sempre reimplementar um destruidor, ou virtual não, nas minhas classes derivadas

Se você estiver usando o compilador do Microsoft C ++, então você pode fazer o seguinte:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

Eu gosto dessa abordagem porque resulta em muito menor código de interface e do tamanho código gerado pode ser significativamente menor. O uso de remove novtable toda referência para o ponteiro vtable nessa classe, para que você nunca pode instanciar diretamente. Consulte a documentação aqui -. novtable

Um pouco além do que está escrito lá em cima:

Em primeiro lugar, verifique se o destruidor é também pura virtual

Em segundo lugar, você pode querer herdar virtualmente (em vez de normalmente) quando você implementar, apenas para boas medidas.

Você também pode considerar classes de contrato implementadas com a NVI (Padrão interface não virtual). Por exemplo:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

Eu ainda sou novo no desenvolvimento de C ++. Comecei com Visual Studio (VS).

No entanto, ninguém parece mencionou a __interface em VS (. NET) . Estou não muito certo se esta é uma boa maneira de declarar uma interface. Mas parece fornecer um aplicação adicionais (mencionado no o documentos ). De tal forma que você não tem que especificar explicitamente o virtual TYPE Method() = 0;, já que ele será convertido automaticamente.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

No entanto, eu não usá-lo porque eu sou preocupação sobre a compatibilidade compilação de plataforma cruzada, uma vez que só está disponível sob .NET.

Se alguém tem nada interessante sobre isso, por favor, compartilhe. : -)

Graças.

Embora seja verdade que virtual é o padrão de fato para definir uma interface, não vamos esquecer o C-como padrão clássico, que vem com um construtor em C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

Isto tem a vantagem de que você pode re-bind eventos tempo de execução sem ter que construir sua classe novamente (como C ++ não tem uma sintaxe para alterar tipos polimórficos, esta é uma solução alternativa para as classes camaleão).

Dicas:

  • Você pode herdar desta como uma classe base (ambos virtual e não-virtual são permitidas) e click preenchimento no construtor do seu descendente.
  • Você pode ter o ponteiro de função como um membro protected e ter uma referência public e / ou getter.
  • Como mencionado acima, isso permite que você alterne a implementação em tempo de execução. Assim, é uma maneira de gerenciar o estado também. Dependendo do número de ifs contra mudanças de estado em seu código, esta pode ser mais rápido que switch()es ou ifs (turnaround é esperado em torno de 3-4 ifs, mas sempre medir pela primeira vez.
  • Se você escolher std::function<> sobre ponteiros de função, você pode ser capaz de gerenciar todos os seus dados de objeto dentro IBase. A partir deste ponto, você pode ter esquemas de valor para IBase (por exemplo, std::vector<IBase> vai funcionar). Note-se que esta pode ser mais lenta dependendo do seu código de compilador e STL; também que as implementações atuais de std::function<> tendem a ter uma sobrecarga quando comparado com ponteiros de função ou até mesmo funções virtuais (esta mudança poderia no futuro).

Aqui é a definição de abstract class em c ++ padrão

n4687

13.4.2

Uma classe abstrata é uma classe que só pode ser utilizada como uma classe base de alguma outra classe; Nenhum objeto de um resumo classe pode ser criado, exceto como subobjects de uma classe derivada dela. A classe é abstrata se ele tem pelo menos uma função virtual pura.

class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

Resultado: área do retângulo: 35 área do triângulo: 17

Vimos como uma classe abstrata definida uma interface em termos de getArea () e duas outras classes implementadas mesma função, mas com um algoritmo diferente para calcular a área específica para a forma.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top