Como configuro uma classe que representa uma interface? Isso é apenas uma classe base abstrata?
Como configuro uma classe que representa uma interface? Isso é apenas uma classe base abstrata?
Respostas:
Para expandir a resposta de bradtgmurray , convém fazer uma exceção à lista de métodos virtuais puros da sua interface adicionando um destruidor virtual. Isso permite que você passe a propriedade do ponteiro para outra parte sem expor a classe derivada concreta. O destruidor não precisa fazer nada, porque a interface não possui membros concretos. Pode parecer contraditório definir uma função como virtual e embutida, mas confie 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 precisa incluir um corpo para o destruidor virtual - alguns compiladores têm problemas para otimizar um destruidor vazio e é melhor usar o padrão.
=0
destruidor virtual ( ) puro com um corpo. A vantagem aqui é que o compilador pode, teoricamente, ver que o vtable não tem membros válidos agora e descartá-lo completamente. Com um destruidor virtual com um corpo, o destruidor pode ser chamado (virtualmente), por exemplo, no meio da construção via this
ponteiro (quando o objeto construído ainda é do Parent
tipo) e, portanto, o compilador deve fornecer uma tabela válida. Portanto, se você não chamar explicitamente os destruidores virtuais this
durante a construção :), poderá economizar no tamanho do código.
override
palavra-chave para permitir argumentos em tempo de compilação e verificar o tipo de valor de retorno. Por exemplo, na declaração de Childvirtual void OverrideMe() override;
Faça uma aula com métodos virtuais puros. Use a interface criando outra classe que substitui esses métodos virtuais.
Um método virtual puro é um método de classe 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
}
};
override
em C ++ 11
Todo o motivo pelo qual você possui uma categoria de tipo de interface especial, além das classes base abstratas em C # / Java, é porque o C # / Java não suporta herança múltipla.
O C ++ suporta herança múltipla e, portanto, não é necessário um tipo especial. Uma classe base abstrata sem métodos não abstratos (virtual puro) é funcionalmente equivalente a uma interface C # / Java.
Thread
instância. A herança múltipla pode ter um design ruim e também uma composição. Tudo depende do caso.
Não existe um conceito de "interface" propriamente dito em C ++. AFAIK, as interfaces foram introduzidas pela primeira vez em Java para solucionar a falta de herança múltipla. Esse conceito acabou sendo bastante útil, e o mesmo efeito pode ser alcançado em C ++ usando uma classe base abstrata.
Uma classe base abstrata é uma classe na qual pelo menos uma função de membro (método no Java Java) é uma função virtual pura declarada usando a seguinte sintaxe:
class A
{
virtual void foo() = 0;
};
Uma classe base abstrata não pode ser instanciada, ou seja, você não pode declarar um objeto da classe A. Você só pode derivar classes de A, mas qualquer classe derivada que não forneça uma implementação de foo()
também será abstrata. Para deixar de ser abstrata, uma classe derivada deve fornecer implementações para todas as funções virtuais puras que herda.
Observe que uma classe base abstrata pode ser mais do que uma interface, porque pode conter membros de dados e funções de membro que não são puramente virtuais. Um equivalente de uma interface seria uma classe base abstrata sem dados com apenas funções virtuais puras.
E, como Mark Ransom apontou, uma classe base abstrata deve fornecer um destruidor virtual, como qualquer classe base, nesse caso.
Até onde pude testar, é muito importante adicionar o destruidor virtual. Estou usando objetos criados new
e destruídos comdelete
.
Se você não adicionar o destruidor virtual na interface, o destruidor da classe herdada não será 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() {};
, verá que o destruidor Tester::~Tester()
nunca é chamado.
Minha resposta é basicamente a mesma que as outras, mas acho que há duas outras coisas importantes a fazer:
Declare um destruidor virtual em sua interface ou faça um destruidor não virtual protegido para evitar comportamentos indefinidos se alguém tentar excluir um objeto do tipo IDemo
.
Use herança virtual para evitar problemas com herança múltipla. (Muitas vezes, há herança múltipla quando usamos interfaces.)
E como outras respostas:
Use a interface criando outra classe que substitui esses 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
}
}
No C ++ 11, você pode facilmente evitar completamente a herança:
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;
// ...
};
Nesse caso, uma interface possui semântica de referência, ou seja, você precisa garantir que o objeto sobreviva à interface (também é possível fazer interfaces com semântica de valores).
Esse tipo de interface tem seus prós e contras:
Finalmente, a herança é a raiz de todo mal no design de software complexo. Em Sean Parent, o valor da semântica e do polimorfismo baseado em conceitos (altamente recomendado, são explicadas melhores versões dessa técnica), o seguinte caso é estudado:
Digamos que eu tenha um aplicativo no qual trato de formas polimorficamente usando a MyShape
interface:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
No seu aplicativo, você faz o mesmo com formas diferentes usando a YourShape
interface:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Agora diga que deseja usar algumas das formas que desenvolvi em seu aplicativo. Conceitualmente, nossas formas têm a mesma interface, mas para fazer com que minhas formas funcionem em seu aplicativo, você precisará estender minhas formas da seguinte maneira:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
Primeiro, modificar minhas formas pode não ser possível. Além disso, a herança múltipla leva o caminho para o código espaguete (imagine um terceiro projeto que use a TheirShape
interface ... o que acontece se eles também chamarem sua função drawmy_draw
?).
Atualização: Há algumas novas referências sobre polimorfismo não baseado em herança:
Circle
classe é um projeto pobre. Você deve usar Adapter
padrão nesses casos. Desculpe se vai parecer um pouco duro, mas tente usar alguma biblioteca da vida real como Qt
antes de fazer julgamentos sobre herança. A herança torna a vida muito mais fácil.
Adapter
padrão? Estou interessado em ver suas vantagens.
Square
ainda não está lá? Presciência? É por isso que está separado da realidade. E, na realidade, se você optar por confiar na biblioteca "MyShape", poderá adotar sua interface desde o início. No exemplo de formas, existem muitas bobagens (uma das quais é que você tem duas Circle
estruturas), mas o adaptador seria algo parecido com isso -> ideone.com/UogjWk
Todas as boas respostas acima. Uma coisa extra que você deve ter em mente - você também pode ter um destruidor virtual puro. 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 pela qual você gostaria de fazer isso é se você deseja fornecer métodos de interface, como eu, mas faça a substituição deles opcional.
Para transformar a classe em uma classe de interface, é necessário um método virtual puro, mas todos os seus métodos virtuais têm implementações padrão; portanto, o único método que resta para tornar o virtual puro é o destruidor.
Reimplementar um destruidor na classe derivada não é grande coisa - eu sempre reimplemento um destruidor, virtual ou não, nas minhas classes derivadas.
Se você estiver usando o compilador C ++ da Microsoft, poderá 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 um código de interface muito menor e o tamanho do código gerado pode ser significativamente menor. O uso do novtable remove todas as referências ao ponteiro do vtable nessa classe, para que você nunca possa instancia-lo diretamente. Veja a documentação aqui - novtable .
novtable
o padrãovirtual void Bar() = 0;
= 0;
que adicionei). Leia a documentação se não a entender.
= 0;
e assumi que era apenas uma maneira não-padrão de fazer exatamente o mesmo.
Uma pequena adição ao que está escrito lá em cima:
Primeiro, verifique se o seu destruidor também é virtual
Segundo, você pode querer herdar virtualmente (em vez de normalmente) ao implementar, apenas por boas medidas.
Você também pode considerar as classes de contrato implementadas com o NVI (Non Virtual Interface Pattern). 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 o Visual Studio (VS).
No entanto, ninguém parece mencionar isso __interface
no VS (.NET) . Estou não muito certo se esta é uma boa maneira de declarar uma interface. Mas parece fornecer uma aplicação adicional (mencionada nos documentos ). De modo que você não precisa especificar explicitamente o virtual TYPE Method() = 0;
, pois ele será convertido automaticamente.
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
No entanto, não o uso porque estou preocupado com a compatibilidade de compilação entre plataformas, pois ela está disponível apenas no .NET.
Se alguém tiver algo interessante sobre isso, compartilhe. :-)
Obrigado.
Embora seja verdade que esse virtual
é o padrão de fato para definir uma interface, não vamos esquecer o padrão clássico do tipo C, 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)();
Isso tem a vantagem de poder vincular novamente o tempo de execução de eventos sem precisar construir sua classe novamente (como o C ++ não possui uma sintaxe para alterar tipos polimórficos, é uma solução alternativa para as classes camaleão).
Dicas:
click
o construtor do seu descendente.protected
membro e ter umpublic
referência e / ou getter.if
alterações s vs. estado em seu código, isso pode ser mais rápido que switch()
es ou if
s (espera-se uma recuperação em torno de 3-4if
s, mas sempre meça primeiro.std::function<>
mais de 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 valores para IBase
(por exemplo, std::vector<IBase>
funcionará). Observe que isso pode ser mais lento, dependendo do seu compilador e código STL; também que as implementações atuais std::function<>
tendem a ter uma sobrecarga quando comparadas com ponteiros de função ou até funções virtuais (isso pode mudar no futuro).Aqui está a definição de abstract class
padrão c ++
n4687
13.4.2
Uma classe abstrata é uma classe que pode ser usada apenas como classe base de alguma outra classe; nenhum objeto de uma classe abstrata pode ser criado, exceto como subobjetos de uma classe derivada dela. Uma classe é abstrata se tiver 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 definiu uma interface em termos de getArea () e duas outras classes implementaram a mesma função, mas com algoritmos diferentes para calcular a área específica da forma.