É possível subclasse um struct C em C ++ e ponteiros de uso para a estrutura em código C?

StackOverflow https://stackoverflow.com/questions/127290

  •  02-07-2019
  •  | 
  •  

Pergunta

Existe um efeito colateral em fazer isso:

código C:

struct foo {
      int k;
};

int ret_foo(const struct foo* f){ 
    return f.k; 
}

código C ++:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

Há uma extern "C" em torno do código C ++ e cada código está dentro de sua própria unidade de compilação.

É este o portátil através de compiladores?

Foi útil?

Solução

Este é totalmente legal. Em C ++, classes e estruturas são conceitos idênticos, com a ressalva de que todos os membros da estrutura são públicos por padrão. Essa é a única diferença. Então, perguntar se você pode estender um struct não é diferente do que perguntar se você pode estender uma classe.

Há uma ressalva aqui. Há nenhuma garantia de consistência de layout de compilador para compilador. Então, se você compilar o código C com um compilador diferente do seu código C ++, você pode executar em problemas relacionados com a disposição membro (preenchimento especialmente). Isso pode ocorrer mesmo quando se usa os compiladores C e C ++ do mesmo fornecedor.

I Have tive isso acontecer com gcc e g ++. Eu trabalhei em um projeto que utilizou várias estruturas de grande porte. Infelizmente, g ++ embalado as estruturas significativamente mais flexível do que o gcc, que causou problemas significativos que partilham objectos entre código C e C ++. Nós finalmente teve para definir manualmente embalagem e inserção estofo para fazer a C e C ++ deleite código as estruturas do mesmo. Note, no entanto, que este problema pode ocorrer independentemente da subclasse. Na verdade não estávamos subclasse o struct C neste caso.

Outras dicas

Eu certamente não recomendo o uso de tais subclasses estranho. Seria melhor para mudar seu projeto para composição uso em vez de herança. Basta fazer um membro

foo * m_pfoo;

na classe bar e ele vai fazer o mesmo trabalho.

Outra coisa que você pode fazer é fazer mais uma classe FooWrapper, contendo a estrutura em si, com o método getter correspondente. Depois, você pode subclasse o wrapper. Desta forma, o problema com o processo de destruição virtual está desaparecido.

“Nunca derivar de classes concretas.” - Sutter

“Make classes não-folha abstrata.” - Meyers

É simplesmente errado a subclasse classes não-interface. Você deve refatorar suas bibliotecas.

Tecnicamente, você pode fazer o que quiser, contanto que você não invocar um comportamento indefinido por, e. g., a exclusão de um ponteiro para a classe derivada por um ponteiro para a sua subobjecto classe base. Você não precisa mesmo de extern "C" para o código C ++. Sim, é portátil. Mas é má concepção.

Isto é perfeitamente legal, embora possa ser confuso para outros programadores.

Você pode usar a herança para estender C-estruturas com métodos e construtores.

Amostra:

struct POINT { int x, y; }
class CPoint : POINT
{
public:
    CPoint( int x_, int y_ ) { x = x_; y = y_; }

    const CPoint& operator+=( const POINT& op2 )
    { x += op2.x; y += op2.y; return *this; }

    // etc.
};

Estendendo estruturas pode ser "mais" o mal, mas não é algo que você está proibido de fazer.

Uau, isso é mal.

É este o portátil através de compiladores?

A maioria definitivamente não. Considere o seguinte:

foo* x = new bar();
delete x;

Para que isso funcione, destruidor do foo deve ser virtual que claramente não é. Contanto que você não use new e enquanto o derivado objectd não tem destruidores personalizados, no entanto, você pode ter sorte.

/ EDIT: Por outro lado, se o código é utilizado apenas como na pergunta, a herança não tem vantagem sobre composição. Basta seguir o conselho dado por m_pGladiator.

Isto é perfeitamente legal, e você pode vê-lo em prática com as classes MFC CRect e PontosC. PontosC deriva a partir do ponto (definidos em Windef.h), e deriva CRect de rect. Você está simplesmente decorar um objeto com funções membro. Contanto que você não se estendem o objeto com mais dados, você está bem. Na verdade, se você tem uma estrutura complexa C que é uma dor de padrão-inicializar, estendendo-o com uma classe que contém um construtor padrão é uma maneira fácil de lidar com essa questão.

Mesmo se você fizer isso:

foo *pFoo = new bar;
delete pFoo;

então você está bem, desde que seu construtor e destruidor são triviais, e você não ter alocado qualquer memória extra.

Você também não tem que envolver seu objeto C ++ com 'extern "C"', desde que você não está realmente passando um C ++ tipo para as funções C.

Eu não acho que isso é necessariamente um problema. O comportamento é bem definido, e contanto que você tome cuidado com as questões do tempo de vida (não misturar e combinar as alocações entre o C ++ e código C) vai fazer o que quiser. Deve ser perfeitamente portátil através de compiladores.

O problema com destruidores é real, mas aplica-se a qualquer momento o destruidor de classe base não é virtual não apenas para estruturas C. É algo que você precisa estar ciente de, mas não impede usando esse padrão.

Ela vai trabalhar, e portably mas você não pode usar quaisquer funções virtuais (que inclui destruidores).

Eu recomendo que em vez de fazer isso, você tem Bar conter uma Foo.

class Bar
{
private:
   Foo  mFoo;
};

Eu não entendo por que você não simplesmente fazer ret_foo um método membro. Sua forma atual torna seu código muito difícil de entender. O que é tão difícil sobre o uso de uma classe real no primeiro lugar com uma variável de membro e métodos get / set?

Eu sei que é possível estruturas subclasse em C ++, mas o perigo é que os outros não será capaz de entender o que você codificado porque é tão raro que alguém realmente faz isso. Eu iria para uma solução robusta e comum em seu lugar.

Provavelmente vai funcionar, mas eu não acredito que ele é garantido para. O seguinte é uma citação de ISO C ++ 10/5:

Um subobjecto classe base pode ter uma disposição (3,7) diferente da disposição de um objecto mais derivados do mesmo tipo.

É difícil ver como no "mundo real" isso poderia realmente ser o caso.

EDIT:

O resultado final é que o padrão não tem limitado o número de locais onde uma disposição subobjecto classe base pode ser diferente a partir de um objecto concreto com o mesmo tipo de base de dados. O resultado é que quaisquer suposições que você pode ter, como POD-ness etc. não são necessariamente verdade para a subobject classe base.

EDIT:

Uma abordagem alternativa, e aquele cujo comportamento é bem definido é fazer com que 'foo' um membro da 'bar' e para fornecer um operador de conversão onde é necessário.

class bar {
public:    
   int my_bar() { 
       return ret_foo( foo_ ); 
   }

   // 
   // This allows a 'bar' to be used where a 'foo' is expected
   inline operator foo& () {
     return foo_;
   }

private:    
  foo foo_;
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top