Pergunta

Uma vez que C ++ não possui o recurso interface de Java e C #, que é a forma preferida de interfaces de simular em classes C ++? Meu palpite seria herança múltipla de classes abstratas. Quais são as implicações em termos de sobrecarga de memória / desempenho? Existem quaisquer convenções de nomeação para tais interfaces simuladas, como SerializableInterface?

Foi útil?

Solução

Uma vez que C ++ tem herança múltipla diferente de C # e Java, sim, você pode fazer uma série de classes abstratas.

Como para a convenção, é até você; no entanto, eu gostaria de preceder os nomes de classe com um I.

class IStringNotifier
{
public:
  virtual void sendMessage(std::string &strMessage) = 0;
  virtual ~IStringNotifier() { }
};

O desempenho é nada para se preocupar em termos de comparação entre C # e Java. Basicamente, você só vai ter a sobrecarga de ter uma tabela de referência para as suas funções ou uma vtable assim como qualquer tipo de herança com métodos virtuais teria dado.

Outras dicas

Não há realmente nenhuma necessidade de 'simular' qualquer coisa, pois não é que C ++ é faltando alguma coisa que Java pode fazer com interfaces.

A partir de um ponteiro C ++ de vista, Java faz uma disctinction "artificial" entre um interface e uma class. Um interface é apenas um class todos cujos métodos são abstratos e que não pode conter membros de dados.

Java faz essa restrição, uma vez que não permite herança múltipla sem restrições, mas permite uma class para implement múltiplas interfaces.

C ++, uma class é um class e um interface é um class. extends é conseguido por herança público e implements é também alcançada por herança público.

Herdando de várias classes não-interface podem resultar em complicações extras, mas pode ser útil em algumas situações. Se você restringir-se apenas à herança de classes de no máximo uma classe não-interface e qualquer número de classes totalmente abstratas, então você não vai encontrar quaisquer outras dificuldades do que você teria em Java (outros ++ / diferenças Java C exceção).

Em termos de custos de memória e gerais, se você é re-criar uma hierarquia de classes estilo Java, então você provavelmente já pagou o custo função virtual em suas aulas em qualquer caso. Tendo em conta que você está usando ambientes de execução diferentes de qualquer maneira, não vai haver nenhuma diferença fundamental na sobrecarga entre os dois em termos de custo dos diferentes modelos de herança.

"Quais são as implicações em termos de sobrecarga de memória / desempenho?"

Normalmente, nenhum, exceto os de uso de chamadas virtuais em tudo, embora nada muito é garantida pela norma em termos de desempenho.

Em sobrecarga de memória, a otimização "classe base vazia" permite explicitamente o compilador para estruturas de layout, que a adição de uma classe base que não tem membros de dados não aumenta o tamanho de seus objetos. Eu acho que é improvável que você tem que lidar com um compilador que não fazer isso, mas posso estar errado.

Adicionando a primeira função de membro virtual de uma classe geralmente aumenta objetos do tamanho de um ponteiro, em comparação com se não tivessem funções membro virtuais. Adicionando novas funções membro virtuais não faz diferença adicional. Adicionando classes base virtuais pode fazer uma outra diferença, mas você não precisa que para o que você está falando.

Como adicionar várias classes de base com funções membro virtuais provavelmente significa que, na verdade você só tem a otimização classe base vazia uma vez, porque em uma implementação típica do objeto terá vários ponteiros vtable. Então, se você precisa de múltiplas interfaces em cada classe, você pode estar aumentando o tamanho dos objetos.

Sobre o desempenho, uma chamada de função virtual tem um pouquinho mais sobrecarga do que uma chamada de função não-virtual, e mais importante que você pode assumir que geralmente (sempre?) Não vai ser embutido. Adicionando uma classe base vazia, normalmente, não adicionar qualquer código para construção ou destruição, porque o construtor base vazia e destruidor pode ser embutido no código construtor da classe derivada / destructor.

Existem truques que você pode usar para evitar funções virtuais se você quiser as interfaces explícitas, mas você não precisa de polimorfismo dinâmico. No entanto, se você está tentando emular Java, então eu presumo que não é o caso.

código Exemplo:

#include <iostream>

// A is an interface
struct A {
    virtual ~A() {};
    virtual int a(int) = 0;
};

// B is an interface
struct B {
    virtual ~B() {};
    virtual int b(int) = 0;
};

// C has no interfaces, but does have a virtual member function
struct C {
    ~C() {}
    int c;
    virtual int getc(int) { return c; }
};

// D has one interface
struct D : public A {
    ~D() {}
    int d;
    int a(int) { return d; }
};

// E has two interfaces
struct E : public A, public B{
    ~E() {}
    int e;
    int a(int) { return e; }
    int b(int) { return e; }
};

int main() {
    E e; D d; C c;
    std::cout << "A : " << sizeof(A) << "\n";
    std::cout << "B : " << sizeof(B) << "\n";
    std::cout << "C : " << sizeof(C) << "\n";
    std::cout << "D : " << sizeof(D) << "\n";
    std::cout << "E : " << sizeof(E) << "\n";
}

Saída (GCC em uma plataforma de 32 bits):

A : 4
B : 4
C : 8
D : 8
E : 12

Interfaces em C ++ são classes que têm funções virtuais única puros. Por exemplo. :

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
};

Esta não é uma interface simulado, é uma interface como os de Java, mas não carrega as desvantagens.

por exemplo. você pode adicionar métodos e membros sem consequências negativas:

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
protected:
    void  serialize_atomic( int i, stream& t );
    bool  serialized;
};

Para as convenções de nomenclatura ... não existem convenções de nomenclatura reais definidas na linguagem C ++. Então, escolha aquele em seu ambiente.

A sobrecarga é uma tabela estática e em classes derivadas que ainda não têm funções virtuais, um ponteiro para a tabela estática.

Em C ++ podemos ir mais longe do que o simples interfaces de comportamento menos de Java & co. Podemos acrescentar contratos explícitos (como no Design by Contract ) com o padrão NVI.

struct Contract1 : noncopyable
{
    virtual ~Contract1();
    Res f(Param p) {
        assert(f_precondition(p) && "C1::f precondition failed");
        const Res r = do_f(p);
        assert(f_postcondition(p,r) && "C1::f postcondition failed");
        return r;
    }
private:
    virtual Res do_f(Param p) = 0;
};

struct Concrete : virtual Contract1, virtual Contract2
{
    ...
};

Se você não usar a herança virtual, a sobrecarga não deve ser pior do que a herança regular com pelo menos uma função virtual. Cada classe abstrata inheritted partir irá adicionar um ponteiro para cada objeto.

No entanto, se você faz algo como o vazio classe base Optimization, você pode minimizar isso:

struct A
{
    void func1() = 0;
};

struct B: A
{
    void func2() = 0;
};

struct C: B
{
    int i;
};

O tamanho de C será duas palavras.

A propósito MSVC 2008 tem __interface palavra-chave.

A Visual C++ interface can be defined as follows: 

 - Can inherit from zero or more base
   interfaces.
 - Cannot inherit from a base class.
 - Can only contain public, pure virtual
   methods.
 - Cannot contain constructors,
   destructors, or operators.
 - Cannot contain static methods.
 - Cannot contain data members;
   properties are allowed.

Este recurso é Microsoft específico. Cuidado: __interface não tem destrutor virtual que é necessário se você excluir objetos por seus ponteiros de interface.

Interfaces em C ++ também pode ocorrer estaticamente, documentando os requisitos de parâmetros de tipo de modelo.

Modelos sintaxe padrão de jogo, para que você não tem que especificar na frente que um tipo particular implementa uma interface específica, desde que ele tem os membros direito. Isso está em contraste com <? extends Interface> de Java ou restrições C # 's estilo where T : IInterface, que exigem o tipo substituído saber sobre (I) Interface.

Um grande exemplo disso é o Iterator família, que são implementados por, entre outras coisas, os ponteiros.

Não há nenhuma boa maneira de implementar uma interface do jeito que você está pedindo. O problema com uma abordagem, tais como como completamente sumário ISerializable mentiras classe base no modo como os instrumentos C ++ herança múltipla. Considere o seguinte:

class Base
{
};
class ISerializable
{
  public:
    virtual string toSerial() = 0;
    virtual void fromSerial(const string& s) = 0;
};

class Subclass : public Base, public ISerializable
{
};

void someFunc(fstream& out, const ISerializable& o)
{
    out << o.toSerial();
}

É claro que a intenção é para a função toSerial () para serializar todos os membros da subclasse incluindo aqueles que herda da classe base. O problema é que não há nenhum caminho de ISerializable de Base. Você pode ver isso graficamente se você executar o seguinte:

void fn(Base& b)
{
    cout << (void*)&b << endl;
}
void fn(ISerializable& i)
{
    cout << (void*)&i << endl;
}

void someFunc(Subclass& s)
{
    fn(s);
    fn(s);
}

A saída valor pela primeira chamada não é a mesma que a saída valor pela segunda chamada. Mesmo que uma referência a s é passado em ambos os casos, os ajustes do compilador o endereço passado para corresponder ao tipo de classe base adequada.

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