O termo "interface" em C ++

11

Java faz uma distinção clara entre classe interface. (Acredito que o C # também o faça, mas não tenho experiência com ele). Ao escrever C ++, no entanto, não há distinção imposta por linguagem entre classe e interface.

Consequentemente, sempre vi a interface como uma solução alternativa para a falta de herança múltipla em Java. Fazer essa distinção parece arbitrário e sem sentido em C ++.

Eu sempre tendi a seguir a abordagem "escrever as coisas da maneira mais óbvia"; portanto, em C ++, tenho o que pode ser chamado de interface em Java, por exemplo:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() = 0;
};

e então decidi que a maioria dos implementadores Fooqueria compartilhar algumas funcionalidades comuns que provavelmente escreveria:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() {}
protected:
  // If it needs this to do its thing:
  int internalHelperThing(int);
  // Or if it doesn't need the this pointer:
  static int someOtherHelper(int);
};

O que torna isso não mais uma interface no sentido Java.

Em vez disso, o C ++ possui dois conceitos importantes, relacionados ao mesmo problema de herança subjacente:

  1. virtual herança
  2. Classes sem variáveis ​​de membro não podem ocupar espaço extra quando usadas como base

    "Subobjetos da classe base podem ter tamanho zero"

    Referência

Entre aqueles que tento evitar o número 1, sempre que possível - é raro encontrar um cenário em que esse seja realmente o design "mais limpo". O número 2 é, no entanto, uma diferença sutil, mas importante, entre o meu entendimento do termo "interface" e os recursos da linguagem C ++. Como resultado, atualmente (quase) nunca me refiro a coisas como "interfaces" em C ++ e falo em termos de classes base e seus tamanhos. Eu diria que, no contexto de C ++, "interface" é um nome impróprio.

Chegou ao meu conhecimento que muitas pessoas não fazem essa distinção.

  1. Posso perder alguma coisa permitindo que (por exemplo protected) não- virtualfunções existam em uma "interface" em C ++? (Meu sentimento é exatamente o oposto - um local mais natural para código compartilhado)
  2. O termo "interface" é significativo em C ++ - implica apenas puro virtualou seria justo chamar classes C ++ sem variáveis-membro de interface ainda?
Flexo
fonte
O C # tem a mesma herança múltipla de interfaces, herança única de implementação que Java, mas, com o uso de métodos de extensão genéricos , internalHelperThingpode ser simulado em quase todos os casos.
Sjoerd
Observe que é uma prática recomendada expor membros virtuais.
22412 Klaim
um público ~Foo() {}em uma classe abstrata é um erro em (quase) todas as circunstâncias.
Wolf

Respostas:

11

No C ++, o termo "interface" não possui apenas uma definição amplamente aceita - portanto, sempre que você for usá-lo, você deve dizer exatamente o que quer dizer - uma classe base virtual com ou sem implementações padrão, um arquivo de cabeçalho, membros públicos de uma classe arbitrária e assim por diante.

Com relação ao seu exemplo: em Java (e semelhante em C #), seu código provavelmente implicaria uma separação de preocupações:

interface IFoo {/*  ... */} // here is your interface

class FooBase implements IFoo 
{
     // make default implementations for interface methods
}

class Foo extends FooBase
{
}

No C ++, você pode fazer isso, mas não é obrigado a fazê-lo. E se você quiser chamar uma classe de interface, se ela não tiver variáveis ​​de membro, mas contiver implementações padrão para alguns métodos, basta fazê-lo, mas verifique se todos com quem você está falando sabem o que você quer dizer.

Doc Brown
fonte
3
Outro significado possível de "interface", para um programador C ++, são as publicpartes do .harquivo.
David Thornley
Qual idioma é seu exemplo de código? Se isso é uma tentativa de ser C ++, eu estou prestes a chorar ...
Qix - MONICA FOI ERRADA EM 20/12
@ Qix: fique tranquilo, leia minha postagem novamente (afirma claramente que o código está em Java) e, se você tiver mais de 2000 pontos, poderá editar minha postagem para adicionar o código de exemplo C ++ equivalente, para tornar meu exemplo mais Claro.
Doc Brown
Se isso é java, ainda está errado, e não, não posso editá-lo; caracteres insuficientes para mudar. Java não usar dois pontos na implementação / estendendo, é por isso que eu queria saber se era uma tentativa de C ++ ...
Qix - MONICA foi maltratado
@Qix: se você encontrar problemas mais sintáticas, você pode mantê-los como um presente de Natal :-)
Doc Brown
7

Parece que você pode ter caído na armadilha de confundir o significado do que é que uma interface é conceitualmente como uma implementação (a interface - minúscula 'i') e uma abstração (Uma Interface - maiúscula 'eu' )

Com relação aos seus exemplos, seu primeiro bit de código é apenas uma classe. Embora sua classe tenha uma interface no sentido de fornecer métodos para permitir acessos ao seu comportamento, não é uma Interface no sentido de uma declaração de Interface que fornece uma camada de abstração representando um tipo de comportamento que você pode querer que as classes implemento. A resposta do Doc Brown à sua postagem mostra exatamente o que estou falando aqui.

As interfaces são frequentemente apontadas como a "solução alternativa" para idiomas que não suportam herança múltipla, mas acho que isso é mais um equívoco do que uma verdade difícil (e suspeito que possa ser inflamado por afirmar isso!). As interfaces realmente não têm nada a ver com herança múltipla, pois elas não exigem que sejam herdadas ou sejam um ancestral para fornecer compatibilidade funcional entre classes ou abstração de implementação para classes. Na verdade, eles podem permitir que você efetivamente elimine completamente a herança, caso deseje implementar seu código dessa maneira - não que eu o recomende inteiramente, mas estou apenas dizendo que você poderiafaça. Portanto, a realidade é que, independentemente de questões de herança, as Interfaces fornecem um meio pelo qual os tipos de classe podem ser definidos, estabelecendo as regras que determinam como os objetos podem se comunicar, permitindo que você determine o comportamento que suas classes devem suportar sem ditando o método específico usado para implementar esse comportamento.

Devo perder alguma coisa permitindo que (por exemplo, funções não virtuais protegidas) existam em uma "interface" em C ++? (Meu sentimento é exatamente o oposto - um local mais natural para código compartilhado)

Uma interface pura deve ser inteiramente abstrata, porque permite a definição de um contrato de compatibilidade entre classes que pode não necessariamente ter herdado seu comportamento de um ancestral comum. Quando implementado, você deseja escolher se deseja permitir que o comportamento da implementação seja estendido em uma subclasse. Se seus métodos não forem virtual, você perde a capacidade de estender esse comportamento posteriormente, caso decida criar classes descendentes. Independentemente de a implementação ser virtual ou não, a Interface define a compatibilidade comportamental por si só, enquanto a classe fornece a implementação desse comportamento para as instâncias que a classe representa.

O termo "interface" é significativo em C ++ - implica apenas virtual puro ou seria justo chamar classes C ++ sem variáveis-membro de interface ainda?

Perdoe-me, mas já faz muito tempo que eu realmente escrevi um aplicativo sério em C ++. Lembro-me de que Interface é uma palavra-chave para fins de abstração, como os descrevi aqui. Eu não chamaria classes C ++ de nenhum tipo de Interface, mas diria que a classe tem uma interface dentro dos significados que descrevi acima. Nesse sentido, o termo é significativo, mas isso realmente depende do contexto.

S.Robins
fonte
1
+1 para a distinção entre "ter" e "ser" uma interface.
Doc Brown
6

As interfaces Java não são uma "solução alternativa", são uma decisão deliberada de design para evitar alguns dos problemas com herança múltipla, como herança de diamante, e incentivar práticas de design que minimizem o acoplamento.

Sua interface com métodos protegidos é um caso de necessidade de "preferir composição a herança". Para citar Sutter e Alexandrescu, na seção com esse nome, de seus excelentes padrões de codificação em C ++ :

Evitar impostos sobre herança: herança é o segundo relacionamento de acoplamento mais rígido em C ++, perdendo apenas para amizade. O acoplamento apertado é indesejável e deve ser evitado sempre que possível. Portanto, prefira composição a herança, a menos que você saiba que este último realmente beneficia seu design.

Ao incluir as funções auxiliares em sua interface, você pode economizar um pouco de digitação agora, mas está introduzindo acoplamentos que o prejudicarão no futuro. É quase sempre melhor a longo prazo separar e passar as funções auxiliares em a Foo*.

O STL é um bom exemplo disso. O maior número possível de funções auxiliares é extraído, em <algorithm>vez de pertencer às classes de contêineres. Por exemplo, como sort()usa a API de contêiner público para fazer seu trabalho, você sabe que pode implementar seu próprio algoritmo de classificação sem precisar alterar nenhum código STL. Esse design permite bibliotecas como o boost, que aprimoram o STL em vez de substituí-lo, sem que o STL precise saber algo sobre o boost.

Karl Bielefeldt
fonte
2

Devo perder alguma coisa permitindo que (por exemplo, funções não virtuais protegidas) existam em uma "interface" em C ++? (Meu sentimento é exatamente o oposto - um local mais natural para código compartilhado)

Você pensaria isso, mas colocar um método não virtual protegido em uma classe abstrata, de outra forma, determina a implementação para quem escreve uma subclasse. Fazer isso derrota o propósito de uma interface em seu sentido puro, que é fornecer um verniz que oculta o que está por baixo.

Esse é um daqueles casos em que não há uma resposta única e você precisa usar sua experiência e julgamento para tomar uma decisão. Se você puder dizer com 100% de confiança que todas as subclasses possíveis de sua classe totalmente virtual Foosempre precisarão sempre de uma implementação do método protegido bar(), então Fooé o lugar certo para isso. Depois de ter uma subclasse Bazque não precisa bar(), você precisa conviver com o fato de que Bazterá acesso ao código que não deveria ou passar pelo exercício de reorganizar sua hierarquia de classes. O primeiro não é uma boa prática e o segundo pode levar mais tempo do que os poucos minutos necessários para organizar as coisas corretamente em primeiro lugar.

O termo "interface" é significativo em C ++ - implica apenas virtual puro ou seria justo chamar classes C ++ sem variáveis-membro de interface ainda?

A Seção 10.4 do padrão C ++ faz uma menção passageira ao uso de classes abstratas para implementar interfaces, mas não as define formalmente. O termo é significativo em um contexto geral de ciência da computação, e qualquer pessoa competente deve entender que " Fooé uma interface para (o que quer que seja)" implica alguma forma de abstração. Aqueles com exposição a idiomas com construções de interface definidas podem pensar em virtual puro, mas qualquer pessoa que precise realmente trabalhar com Fooisso analisará sua definição antes de prosseguir.

Blrfl
fonte
2
Qual é a diferença entre fornecer uma declaração virtual pura e uma declaração mais implementação? Nos dois casos, deve haver uma implementação e bazsempre pode ter uma implementação como return false;ou o que quer. Se o método não se aplicar a todas as subclasses, ele não pertence à classe abstrata de base, de nenhuma forma.
David Thornley
1
Acho que sua última frase diz que estamos na mesma página: não há razão para que você não possa ter métodos protegidos em classes abstratas, mas eles não devem ser mais altos na árvore de herança do que o absolutamente necessário. Eu apenas acho que se uma classe precisa adotar uma implementação, ela não está no lugar certo na árvore.
Blrfl