É possível subclasse um struct C em C ++ e ponteiros de uso para a estrutura em código C?
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?
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_;
};