thegoodfellas/blogset-br huggingface dataset:
manutencao em codigos antigos ao longo da vida, percebi que os warnings do tipo signed/unsigned mismatch sao extremamente comuns. Acredito que isso se deva ao fato de que situacoes de comparacao entre variaveis signed e unsigned acontecem com frequencia, e como estes warnings parecem inofensivos, ninguem lhes da atencao. Mas nao e bem assim. Todo warning deve ser levado a serio. Observe os seguintes exemplos:Ex. 1:int x = -1;unsigned int y = 10U;if ( x > y ){ printf("X eh maior");}else{ printf("Y eh maior");} Ex. 2:signed char x = -1;unsigned char y = 10U;if ( x > y ){ printf("X eh maior");}else{ printf("Y eh maior");} Os dois exemplos sao praticamente iguais. A unica diferenca e que, no primeiro, foram utilizadas variaveis do tipo int. No segundo, foram utilizadas variaveis do tipo char.Rodando os exemplos em um PC, obtemos os seguintes resultados:Ex. 1:X eh maiorEx. 2:Y eh maiorMas como???Bom, vamos la. Quando voce faz uma operacao qualquer (soma, subtracao, comparacao...), a regra diz que a operacao sera feita em uma base com pelo menos o tamanho do maior dos operandos. Em uma mesma base, o tipo unsigned e considerado maior que o tipo signed.Observe a tabela abaixo: Operacao Base em que o calculo sera realizado int32 + int16 int32 ou maior. int8 + uint8 uint8 ou maior. (int16)int8 == uint8 int16 ou maior. int16 - int16 int16 ou maior. Agora voltemos aos exemplos acima. No segundo caso, foi feita uma comparacao entre um signed char e um unsigned char. Estes tipos tem 8 bits em um PC normal (pode variar em alguns raros sistemas embarcados), portanto o compilador e livre para resolver a operacao na base 8 bits ou em uma maior.Por questoes de desempenho, o compilador optou por realizar esta comparacao na base int. Isto porque um processador de 32 bits realiza uma operacao na base 32 mais rapido do que na base 8 (acredito que um processador 64 bits teria realizado a operacao na base 64, mas nao cheguei a testar isso). Como (int)-1 e menor que (int)10, o resultado da operacao foi que Y e maior.Agora, no primeiro exemplo, foi feita a comparacao entre um int e um unsigned int. Como o unsigned tem preferencia sobre o tipo signed, o compilador precisava realizar a operacao em uma base de tamanho maior ou igual a unsigned int. Como nao haveria nenhum ganho de desempenho se a operacao fosse realizada em uma base maior que esta, a operacao foi realizada em unsigned int.Assim, o valor (int)-1 foi convertido para (unsigned int)-1, que vale 0xFFFFFFFF (considerando que o int tenha tamanho 32) ou 0xFFFFFFFFFFFFFFFF (em plataforma 64 bits).Em ambos os casos, o valor de X sera maior que o de Y, causando este comportamento estranho.Bom, muito interessante. Se voce nao esta convencido ainda, vou mostrar um exemplo pratico de um problema que eu tive uma vez por causa destas comparacoes incorretas.Em uma empresa X que eu trabalhei ha muito tempo atras, havia um codigo em producao que, de vez em quando, imprimia um monte de lixo na tela e travava. Este problema acontecia
muito raramente, era dificil de se reproduzir e nunca foi encontrado nada de errado no codigo que pudesse causar este tipo erro. Para ajudar, alguns meses antes havia sido feito um trabalho bem-sucedido para remover todos os warnings do fonte. Desta forma, o codigo compilava bonito, com zero erros e zero warnings. Concluiu-se, entao, que o problema devia ser de hardware. Ate que, um dia, mexendo em alguma coisa nada a ver, observei a seguinte linha de codigo:for (unsigned int x = 0U; x < (unsigned int)str.GetLen(); ++x )Aparentemente ok, mas aquele cast para unsigned int me deixou com uma pulga atras da orelha. Olhei o historico deste codigo no SVN e vi que, antes da Operacao Zero Warnings, o codigo era da seguinte forma: for (unsigned int x = 0U; x < str.GetLen(); ++x )Este codigo faz exatamente o que o novo faz, porem, ao ser compilado, da um alerta dizendo "signed/unsigned mismatch on line XXX" que, em outras palavras, e o compilador dizendo "Cuidado, voce pode estar fazendo cagada". Ao colocar aquele cast, o que o desenvolvedor fez foi dizer ao compilador "Ta bom, eu sei que eu to fazendo cagada, nao me encha mais o saco". Se este desenvolvedor tivesse prestado atencao no warning, em vez de simplesmente tentar remove-lo de qualquer forma, teria resolvido o bug milenar.O problema e que a variavel str era do tipo MyString, uma classe que era definida da seguinte forma:class MyString{public: int GetLen() { int retorno = -1; if ( mpDados != NULL ) { retorno = (int)strlen( mpDados ); } return retorno; }...private: char* mpDados;...};Olha so! A GetLen() retorna -1 quando mpDados aponta para NULL!Isso fazia com que o loop fosse executado 4294967295 vezes quando GetLen() retornasse -1, fazendo com que um monte de lixo fosse impresso na tela, ate o codigo travar de vez.O correto, nesta situacao, seria mudar o tipo de "x" para int, em vez de converter o retorno de GetLen() para unsigned.Por isso, da proxima vez, olhem seus warnings com mais atencao :)(P.s.: Sim, o cast de strlen para int tambem nao e muito bacana, mas isso ja e outra historia...) .
pode ser explicado por um contexto historico. O bom e velho Assembly nao possui if/then/else, while, for ou coisa do tipo. O Fortran, nos seus primordios, tambem nao possuia. Todos os lacos e decisoes do codigo eram feitos por meio de go tos. Assim, os primeiros programadores de C estavam acostumados com gotos e os utilizavam sem do. Isso e um convite para transformar seu codigo em um belo dum espaguete, impossivel de ser compreendido. Porem os tempos sao outros. Usar gotos para decisoes e lacos e, sim, uma bela duma merda, mas existem situacoes em que o goto pode te ajudar a construir um codigo mais limpo.Vamos ao exemplo:Exemplo 1://Procurando Nemofor ( i = 0; i < 1000; ++i ){ for ( j = 0; j < 1000; ++j ) { for ( k = 0; k < 1000; ++k ) { //faz milhares de coisas aqui if ( oceano[i][j][k] == NEMO ) { //Encontrei! Quero cair fora daqui! } } }}Ok, voce encontrou Nemo. E agora, como sair dessa?Se colocar um break, somente o laco do k sera cortado. O restante continuara sendo executado.Outra solucao seria marcar uma flag como nemoEncontrado = true dentro do if e verificar em cada for, mas nao seria la a solucao mais bonita do mundo.E por que nao colocar um goto aqui?//Procurando Nemofor ( i = 0; i < 1000; ++i ){ for ( j = 0; j < 1000; ++j ) { for ( k = 0; k < 1000; ++k ) { //faz milhares de coisas aqui if ( oceano[i][j][k] == NEMO ) { //Encontrei! Quero cair fora daqui! goto FimBuscaNemo; } } }}FimBuscaNemo:...Pronto, um bom uso do goto :)Outro exemplo seria para funcoes com multiplos retornos.E uma boa pratica que cada funcao possua apenas 1 ponto de retorno, mas as vezes isso acarreta em um monte de ifs encadeados. Mais uma vez o goto pode te ajudar:Exemplo 2:int func(){ AlocaUmMonteDeCoisaAqui(); if ( abrirArquivo() == false ) { DesalocaTudoQueAlocou(); return ERRO_ABRIR_ARQUIVO; } if ( arquivoVazio() == true ) { DesalocaTudoQueAlocou(); return ERRO_ARQUIVO_VAZIO; } if ( checkSum() == false ) { DesalocaTudoQueAlocou(); return ERRO_CHECKSUM; } if ( tamanhoArquivo() < 100 ) { DesalocaTudoQueAlocou(); return ERRO_TAMANHO_INVALIDO; } //faz um monte de coisa DesalocaTudoQueAlocou(); return BELEZA;}Este e um exemplo muito comum no dia a dia. A funcao aloca um monte de coisa no comeco (poderia bloquear semaforos tambem) e para cada retorno precisa desalocar tudo. E tambem um erro muito comum um programador ir dar manutencao, colocar um novo return em algum ponto e esquecer de chamar a DesalocaTudoQueAlocou().Uma funcao mais elegante e segura poderia ser escrita como:int func(){ int retval = BELEZA; AlocaUmMonteDeCoisaAqui(); if ( abrirArquivo() == false ) { retval = ERRO_ABRIR_ARQUIVO; goto Fim_Func; } if ( arquivoVazio() == true ) { retval = ERRO_ARQUIVO_VAZIO; goto Fim_Func; } if ( checkSum() == false ) { retval = ERRO_CHECKSUM; goto Fim_Func; } if ( tamanhoArquivo() < 100 ) { retval = ERRO_TAMANHO_INVALIDO; goto Fim_Func; } //faz um monte de coisaFim_Func: DesalocaTudoQueAlocou(); return retval;}Ai esta, outro uso saudavel do goto!Ou seja, nao precisa ter medo
da linguagem. Basta apenas usa-la corretamente.Duas regras basicas devem ser seguidas quanto ao uso desta funcao:1 So salte para linhas a frente da atual, e nao para tras;2 Possua, no maximo, dois pontos de destino em cada funcao.Com isso, voce pode se divertir a vontade!E isso!P.s.: Como somos politicamente incorretos, nao poderiamos deixar de falar do COMEFROM, que e basicamente o oposto do GOTO!O COMEFROM surgiu como uma piada de Assembly:http://c2.com/cgi/wiki?ComeFromNao possui nenhuma utilidade a nao ser baguncar seu codigo, mas se voce quiser se exibir para os outros, aqui vai um port do COMEFROM para C:#define _(A) goto A;#define COME_FROM(A) A:int main(){ int x = 0; int y = 0; COME_FROM( fim_loop ); ++x; ++y; _( fim_loop ) return 0;}Boa sorte! :)
defeitos por linha de codigo tiveram um tempo de desenvolvimento menor do que os com maiores indices de defeito, porem com um custo igual (Code Complete, McConnell, 2004).Outras opinioes?A IBM revelou que cada hora investida em inspecao de codigo previne cerca de 100 horas de trabalho em testes e correcoes de defeitos;Raytheon reduziu o custo de correcoes de defeitos de 40% do custo total do projeto para 20% atraves de uma iniciativa de inspecoes (Haley 1996);a HP reportou que seu programa de inspecao economiza em media 22 milhoes de dolares por ano (Grady e Van Slack, 1994);11 programas foram desenvolvidos pela mesma equipe de desenvolvimento. 5 deles foram desenvolvidos sem utilizar revisao de codigo e tiveram uma media de 4,5 erros por 100 linhas de codigo. Os outros 6 foram inspecionados e e tiveram uma media de 0,82 erros por 100 linhas de codigo (Freedman e Weinberg, 1990).Alem disso, outra vantagem da revisao de codigo e a troca de conhecimento entre a equipe. Tanto quem desenvolveu quanto quem esta revisando o codigo podem aprender bastante com o processo.Mas que maravilha! E entao, como e o processo de revisao de codigo?Varias tecnicas de revisao de codigo podem ser aplicadas. Atualmente, um dos modelos de desenvolvimento agil mais conhecidos, o eXtreme Programming, utiliza o pair programming, que e um tipo de inspecao de codigo on-the-fly.Vou explicar aqui um processo formal de revisao. Voce pode adapta-lo conforme a sua necessidade, porem nao recomendaria grandes mudancas se sua empresa ainda nao esta habituada com a pratica.Processo de Revisao Antes de explicar o processo, gostaria de ressaltar duas coisas: Para sistemas simples, uma pessoa consegue revisar de 400 a 500 linhas de codigo por hora. Para sistemas criticos, complexos ou embarcados, esta media cai para cerca de 150 linhas por hora (nestas linhas nao consideramos comentarios ou linhas em branco);Um revisor aguenta revisar um codigo por, no maximo, 2h seguidas. Dito isso, vamos as pessoas envolvidas no processo:Moderador: E o responsavel por fazer o processo andar. Ele deve mover a revisao a uma velocidade que seja rapida o suficiente para ser produtiva, porem nao tao rapida a ponto de que os defeitos nao sejam encontrados. Tambem e o responsavel por organizar as reunioes e definir quem deve revisar o que.Autor: A pessoa que desenvolveu o codigo. Participa das reunioes e deve ser o responsavel por posteriormente aplicar as correcoes.Revisor: E a pessoa que ira revisar o codigo (ou o design, se for o caso). Se for fazer revisao de codigo, deve ser um programador qualificado, de preferencia que esteja envolvido com o processo, mas, obviamente, que NAO seja o autor!Escrituario (ta, foi a melhor traducao que eu encontrei para scribe!): pessoa responsavel por escrever um relatorio com os problemas encontrados durante a reuniao. Esta pessoa pode ser o proprio revisor, mas e comum utilizar outra pessoa para liberar o revisor para suas outras atividades. E uma pratica comum utilizar estagiarios para este fim, com o intuito de que eles, desta forma, adquiram conhecimento e experiencia (ok, as empresas utilizam estagiarios porque sao mais baratos do que os
desenvolvedores, mas eu nao queria chegar neste ponto...).Nao e comum que gerentes participem das revisoes de codigo, pois isso pode constranger os outros participantes, principalmente se o gerente da equipe for um idiota (se voce e gerente, voce deve saber se a sua equipe te considera um idiota ou nao). Os gerentes podem e devem acompanhar os resultados da revisao, mas nao devem acompanhar o processo para que o foco da pratica nao passe de tecnico para politico.As fases da revisao sao:Planejamento: O autor entrega o codigo (ou seja la o que for ser revisado) para o moderador. Este decide quem deve realizar a revisao e quando. Dependendo da quantidade de trabalho, e ate aconselhavel que mais de um revisor seja convocado. O moderador deve imprimir o codigo e reunir quaisquer outros documentos que considere importante.Overview: Quando os revisores nao sao familiarizados com o projeto, e possivel marcar uma reuniao com os revisores e o autor, para que este de uma visao geral do codigo que eles irao revisar. Esta reuniao deve durar no maximo 1h, mas deve ser evitada se possivel.Preparacao: Cada revisor trabalha sozinho eu seu codigo ou modelo que estiver revisano. E interessante que a empresa possua um checklist para facilitar o trabalho, ainda mais se a empresa segue um padrao de desenvolvimento. Os revisores devem estar cientes das duas coisas que ressaltei no comeco do topico (quantidade de linhas para revisar por hora e tempo maximo que deve durar uma revisao).Reuniao de Inspecao: Nesta reuniao, o moderador escolhe alguem (alem do autor) para ler o codigo e explica-lo. E interessante que o codigo esteja projetado na parede ou em um quadro, para que todos possam ve-lo. Assim que o codigo e passado, os erros encontrados sao levantados e discutidos com a equipe. O escrituario e responsavel por registra-los. O moderador e responsavel por manter a reuniao produtiva e evitar que a equipe comece a discutir assuntos irrelevantes a revisao.Esta reuniao nao deve durar mais do que duas horas.Relatorio de Inspecao: Um relatorio com os problemas encontrados deve ser formalizado e enviado para todos os participantes. O relatorio deve conter a lista dos defeitos e a severidade de cada um. Este relatorio pode ser um documento de texto enviado para todos por e-mail ou um registro formal em algum bugtracker, nao importa.Retrabalho: O moderador distribui os defeitos encontrados para a equipe. Em geral o autor e responsavel por corrigi-los, mas nao necessariamente. Follow-up: O moderador e responsavel por acompanhar o andamento da correcao dos defeitos encontrados. Dependendo da quantidade e da complexidade dos defeitos encontrados, um novo processo de revisao pode ser necessario na sequencia. E isso pessoal. Uma coisa que se deve ter em mente e que a revisao de codigo e apenas um processo de qualidade de software, assim como os testes do sistema, e NAO deve ser utilizado para outros fins alem deste. Um erro comum que leva o processo ao fracasso e utilizar o resultado das revisoes para julgar ou humilhar o autor. Ou seja, dar um chapeu de burro porque alguem cometeu um erro
Diz a lenda que o seguinte codigo da um erro em tempo de execucao:#include <cstring>class Foo{public: Foo ( int size ) : mSize(size), mValues( new char[mSize] ) { memset( mValues, 0, mSize ); } ~Foo () { delete[] mValues; } private: char* mValues; const int mSize;};int main(){ Foo deu(10); return 0;}Voce sabe dizer qual e?O problema aqui esta na alocacao de memoria para mValues. Observe o construtor:Foo ( int size ) : mSize(size), mValues( new char[mSize] ) { memset( mValues, 0, mSize ); } Aparentemente esta ok, nao e? No exemplo acima, o objeto e criado passando 10 como parametro. Sendo assim, o valor 10 e atribuido para mSize, e depois mValues e alocado com o tamanho de mSize, e entao e feito um memset para zerar todos os valores de mValues. Muito bonito, mas o problema esta na ordem de inicializacao das variaveis. Quem define esta ordem e a ordem em que as variaveis foram declaradas dentro da classe.Note:private: char* mValues; const int mSize;Nao importa se voce vai chamar Foo ( int size ) : mSize(size), mValues( new char[mSize] ){ ...} ou Foo ( int size ) : mValues( new char[mSize] ), mSize(size){ ...} que mValues sempre vai ser inicializado antes de mSize.Para resolver o problema?Inverta a ordem de declaracao das variaveis.O seguinte codigo:#include <cstring>class Foo{public: Foo ( int size ) : mSize(size), mValues( new char[mSize] ) { memset( mValues, 0, mSize ); } ~Foo () { delete[] mValues; } private: const int mSize; char* mValues;};int main(){ Foo deu(10); return 0;}Nao apresenta problema algum.Dizem que e uma boa pratica inicializar as variaveis no construtor na mesma ordem que elas foram declaradas na classe para evitar este tipo de duvida. Se voce tiver paciencia para faze-lo, mande bala! :) Embora a heranca em si seja um tema basico da orientacao a objetos, ela possui uma serie de artimanhas e regras (principalmente para quem programa em C++) que devem ser seguidas para nao cair em futuras armadilhas.Basicamente, existem tres tipos de herancas em C++: a publica, a protegida e a privada. O primeiro tipo e o mais conhecido. Os outros dois nao sao muito utilizados e raramente sao ensinados na faculdade ou nos cursos de C++. Alem disso, a heranca pode ser virtual ou nao.Vou explicar cada um destes tipos na sequencia:Heranca publica:De longe a mais conhecida e mais utilizada (embora muitas vezes utilizada de forma incorreta). A regra mais importante para esta heranca e: quando voce diz que B herda de A, voce esta dizendo que B e um A. Tenha isso em mente. Sempre. So herde uma classe de outra se voce puder dizer, no bom portugues, que a classe filha e um tipo da classe mae. Nunca herde com o objetivo de economizar codigo ou coisa do tipo.Por exemplo, voce pode herdar Carro de Veiculo, Moto de Veiculo, Ferrari de Carro, Marte de Planeta, Carrefour de Supermercado, mas nunca deve herdar Moto de Carro, por exemplo. Mesmo que no seu codigo seja tentador fazer isso, pois a maioria das funcoes de Moto estao implementadas em Carro, nao faca tal heranca.
O ideal e criar uma classe acima de Carro e Moto (como Veiculo) e colocar la as funcoes em comum. Vou citar um exemplo de um codigo que eu trabalhei ha algum tempo que fazia o uso incorreto de heranca publica.Tinhamos uma classe chamada Thread, com um metodo virtual puro chamado Run, um Inicia e um SetDelay. Basicamente, se voce queria ter uma thread no sistema, bastava herdar desta classe, implementar o metodo Run, e chamar o Inicia para que o conteudo de Run ficasse sendo executado de acordo com o delay definido. Como o codigo implementava uma comunicacao por Gprs e esta comunicacao deveria ficar sendo executada em uma thread separada no sistema, fizemos Gprs herdar de Thread. O problema e que Gprs nao e uma Thread! Gprs pode ser considerado uma Rede, um Canal de Comunicacao, mas definitivamente nao e uma thread! Apesar de parecer inofensiva, a decisao de herdar Gprs de Thread comecou a causar dores de cabeca. O problema e que no codigo voce acaba tendo algo do tipo:Gprs* objGprs = new Gprs();objGprs->SetDelay( 1000 );objGprs->Inicia();Agora, para uma pessoa que nunca mexeu no codigo, o que o Inicia faz? A pessoa pode imaginar que vai configurar IP, zerar as variaveis da classe, ligar o hardware do modem, o que for, mas nao vai imaginar que este comando esta iniciando a Thread. Alem disso, tinhamos a necessidade de ter uma funcao que iniciasse o modem e configurasse a rede, mas esta funcao nao poderia se chamar Inicia porque o Inicia ja esta implementado em Thread. Assim, o codigo acabou ficando uma beleza do tipo: Gprs* objGprs = new Gprs();objGprs->IniciaGprs( IP, PORTA );objGprs->SetDelay( 1000 );objGprs->Inicia();O que beira a estupidez.O correto, neste caso, seria ter criado duas classes: uma chamada ThreadGprs que herda de Thread, e outra chamada Gprs que nao herda de ninguem (ou herda de CanalComunicacao, ou algo do tipo), e fazer com que ThreadGprs agregasse Gprs. Um codigo do tipoThreadGprs* objThreadGprs = new ThreadGprs();objThreadGprs->SetDelay( 1000 );objThreadGprs->Inicia();Faz sentido pra qualquer novato que bata o olho nele. Bom, dito isso, vamos ao que interessa: o que e uma heranca publica?O funcionamento e simples: a classe filha vai conseguir acessar os atributos e metodos publicos e protegidos da mae, e as outras classes vao conseguir acessar os atributos e metodos publicos da filha e da mae atraves de um objeto da filha.Veja o exemplo:class Base{public: void FuncaoUm() { printf( "Funcao numero 1\n" ); } virtual void FuncaoDois() { printf( "Funcao numero 2 da base\n" ); }};class Derivada: public Base{public: void FuncaoDois() { printf( "Funcao numero 2 da derivada!\n" ); }};int main(){ Base* obj = new Derivada(); obj->FuncaoUm(); obj->FuncaoDois(); delete obj; return 0;}O resultado disso vai ser:Funcao numero 1Funcao numero 2 da derivada!(Se voce nao entende o que a palavra virtual faz, experimente rodar o codigo novamente sem ela e vera que o comportamento sera diferente do esperado. Via de regra, sempre que voce pretende reescrever uma funcao na derivada, declare a funcao da Base como virtual).Funcao virtual puraSe voce quer obrigar a Derivada a reimplementar uma funcao, voce pode declara-la como virtual
privada. Existem duas razoes para voce optar por heranca privada:Caso voce precise que a sua classe derivada acesse os metodos e atributos protegidos da base. Isso nao seria possivel caso fosse utilizado composicao ou agregacao, a menos que voce declarasse a classe agregada como friend da classe agregadora.Caso voce queira declarar na base uma funcao virtual (tanto faz se e pura ou nao) e queira que ela seja reimplementada na derivada.Sinceramente nao consigo pensar em nenhum exemplo pratico bom para se utilizar este tipo de heranca.Uma situacao, talvez, fosse no exemplo do Gprs que eu comentei acima. Fazer Gprs herdar de forma privada de Thread seria melhor do que fazer a heranca publica, porque, desta forma, quem chamasse:objGprs->Inicia();Estaria, com certeza, chamando a Inicia do Gprs, e nao da Thread. A diferenca e que dai a responsabilidade de inicializar a thread, definir timeouts e tudo mais ficaria a cargo da classe Gprs. Heranca ProtegidaDos tres tipos de heranca, este e o pior. Ninguem sabe ao certo o que significa dizer que B faz uma heranca protegida de A, mas esta perto de ser um relacionamento do tipo B tem A, como na heranca privada.O funcionamento deste tipo de heranca e bem semelhante ao da (heranca) privada. Os metodos da Base continuam inacessiveis por quem tenta acessa-los por um objeto da Derivada. A diferenca esta em se voce herdar de Derivada (o exemplo da DerivadaDaDerivada). Neste caso, a DerivadaDaDerivada conseguira acessar os metodos e atributos publicos e protegidos da Base sem problemas.Heranca VirtualOs tres tipos citados acima podem ser virtuais ou nao (embora para a heranca privada eu nao veja sentido). As herancas virtuais sao uteis para o caso das herancas do tipo diamante (quem programa em Java treme ao ouvir isso). Uma heranca do tipo diamante se forma quando voce tem uma Base, duas (ou mais) derivadas e depois uma classe que faz heranca multipla dessas duas (ou mais) derivadas. Pense o seguinte:class Base{protected: int x;};class DerivadaUm: public Base{};class DerivadaDois: public Base{};class Join: public DerivadaUm, public DerivadaDois{public: void Funcao() { x = 0; }};Isso vai dar um erro de compilacao, dizendo que o acesso a x e ambiguo. Isso acontece porque Join enxerga dois x: um que veio de Base por meio da DerivadaUm e outro que veio de Base por DerivadaDois.Para que este codigo funcione, e necessario que DerivadaUm e DerivadaDois facam heranca virtual de Base. Assim, o codigo fica da seguinte forma: class Base{protected: int x;};class DerivadaUm: virtual public Base{};class DerivadaDois: virtual public Base{};class Join: public DerivadaUm, public DerivadaDois{public: void Funcao() { x = 0; }};Isso criara apenas uma instancia de x para Join. Bom, dizem que herancas do tipo virtual devem ser evitadas. O problema nao esta com as herancas virtuais, mas sim com a heranca do tipo diamante. Se voce chegar a um modelo que chegue a este tipo de configuracao, evite-o. Modele de forma diferente. Mas, inevitavelmente, voce vai acabar tendo que dar manutencao em um codigo que esta mal modelado, e talvez esta seja a sua unica saida...