Substituir funções virtuais C ++ com segurança

100

Eu tenho uma classe base com uma função virtual e quero substituir essa função em uma classe derivada. Existe alguma maneira de fazer o compilador verificar se a função que declarei na classe derivada realmente substitui uma função na classe base? Gostaria de adicionar alguma macro ou algo que garanta que eu não declarei acidentalmente uma nova função, em vez de substituir a antiga.

Veja este exemplo:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Aqui parent::handle_event()é chamado em vez de child::handle_event(), porque o método da criança perde a constdeclaração e, portanto, declara um novo método. Isso também pode ser um erro de digitação no nome da função ou alguma pequena diferença nos tipos de parâmetros. Também pode acontecer facilmente se a interface da classe base mudar e em algum lugar alguma classe derivada não foi atualizada para refletir a mudança.

Existe alguma maneira de evitar esse problema, posso de alguma forma dizer ao compilador ou alguma outra ferramenta para verificar isso para mim? Quaisquer sinalizadores de compilador úteis (de preferência para g ++)? Como você evita esses problemas?

sth
fonte
2
Ótima pergunta, há uma hora venho tentando descobrir por que minha função de classe filha não é chamada!
Akash Mankar

Respostas:

89

Desde o g ++ 4.7, ele entende a nova overridepalavra-chave C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};
Gunther Piez
fonte
@hirschhornsalz: Eu descobri que quando você implementa a função handle_event e adiciona override no final da implementação da função, g ++ dá um erro; se você fornecer uma implementação de função embutida na declaração de classe após a palavra-chave override, está tudo bem. Por quê?
h9uest
3
@ h9uest overridedeve ser usado na definição. Uma implementação inline é tanto definição quanto implementação, então tudo bem.
Gunther Piez
@hirschhornsalz sim, recebi a mesma mensagem de erro do g ++. Porém, uma nota lateral: você e a mensagem de erro do g ++ usaram o termo "definição de classe" - não devemos usar "declaração" (par de {declaração, definição})? Você deixou-se claro neste contexto em particular ao dizer "definição e implementação", mas eu só me pergunto por que a comunidade c ++ repentinamente decidiu mudar os termos das classes?
05 de
20

Algo como a overridepalavra-chave do C # não faz parte do C ++.

No gcc, -Woverloaded-virtualadverte contra ocultar uma função virtual da classe base com uma função de mesmo nome, mas uma assinatura suficientemente diferente para que não a substitua. No entanto, isso não o protegerá contra a falha em substituir uma função devido à grafia incorreta do próprio nome da função.

CB Bailey
fonte
2
É se você estiver usando Visual C ++
Steve Rowe
4
Usar o Visual C ++ não torna overrideuma palavra-chave em C ++; isso pode significar que você está usando algo que pode compilar algum código-fonte C ++ inválido. ;)
CB Bailey
3
O fato de a substituição ser C ++ inválida significa que o padrão está errado, e não o Visual C ++
Jon
2
@ Jon: OK, agora eu vejo o que você queria dizer. Pessoalmente, eu poderia pegar ou deixar a overridefuncionalidade do estilo C # ; Raramente tive problemas com substituições com falha e eles são relativamente fáceis de diagnosticar e consertar. Uma coisa com a qual não concordo é que os usuários de VC ++ devem usá-lo. Eu prefiro que o C ++ se pareça com o C ++ em todas as plataformas, mesmo que um projeto específico não precise ser portátil. É importante notar que C ++ 0x terá atributos e [[base_check]], portanto, você pode optar por ignorar a verificação, se desejar. [[override]][[hiding]]
CB Bailey
5
Curiosamente, alguns anos depois que o comentário usando VC ++ não cria overrideuma palavra - chave , parece que sim . Bem, não é uma palavra-chave adequada, mas um identificador especial em C ++ 11. A Microsoft se esforçou o suficiente para fazer disso um caso especial e seguir o formato geral para atributos e overridetorná-lo padrão :)
David Rodríguez - dribeas
18

Pelo que eu sei, você não pode simplesmente torná-lo abstrato?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Pensei ter lido em www.parashift.com que você pode realmente implementar um método abstrato. O que faz sentido para mim pessoalmente, a única coisa que faz é forçar as subclasses a implementá-lo, ninguém disse nada sobre não ser permitido ter uma implementação em si.

Ray Hidayat
fonte
Só agora percebi que isso funciona em mais do que apenas destruidores! Ótimo achado.
strager
1
Existem algumas desvantagens potenciais nisso: 1) a outra coisa que marcar um ou mais métodos como abstratos faz com que a classe base não seja instanciada, o que pode ser um problema se não fizer parte do uso pretendido da classe. 2) a classe base pode não ser sua para modificar em primeiro lugar.
Michael Burr
3
Eu concordo com Michael Burr. Tornar a classe base abstrata não faz parte da questão. É perfeitamente razoável ter uma classe base com funcionalidade em um método virtual, que você deseja que uma classe derivada substitua. E é igualmente razoável querer se proteger contra outro programador renomear a função na classe base e fazer com que sua classe derivada não a substitua mais. A extensão de "substituição" da Microsoft é inestimável neste caso. Adoraria vê-lo adicionado ao padrão, porque infelizmente não há uma boa maneira de fazer isso sem ele.
Brian
Isso também evita que o método base (digamos BaseClass::method()) seja chamado na implementação derivada (digamos DerivedClass::method()), por exemplo, para um valor padrão.
Narcolessico
11

No MSVC, você pode usar a overridepalavra-chave CLR mesmo se não estiver compilando para CLR.

No g ++, não há uma maneira direta de impor isso em todos os casos; outras pessoas deram boas respostas sobre como detectar diferenças de assinatura usando -Woverloaded-virtual. Em uma versão futura, alguém pode adicionar sintaxe como __attribute__ ((override))ou equivalente usando a sintaxe C ++ 0x.

Doug
fonte
9

Em MSVC ++ você pode usar palavras-chaveoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override funciona tanto para código nativo quanto para código CLR em MSVC ++.

bobobobo
fonte
5

Torne a função abstrata, de forma que as classes derivadas não tenham outra escolha a não ser substituí-la.

@Ray Seu código é inválido.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Funções abstratas não podem ter corpos definidos em linha. Deve ser modificado para se tornar

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }
Tanveer Badar
fonte
3

Eu sugeriria uma pequena mudança em sua lógica. Pode ou não funcionar, dependendo do que você precisa realizar.

handle_event () ainda pode fazer o "código padrão chato", mas em vez de ser virtual, no ponto onde você deseja que ele faça o "novo código interessante", faça com que a classe base chame um método abstrato (ou seja, deve ser substituído). que será fornecido por sua classe descendente.

EDITAR: E se mais tarde você decidir que algumas de suas classes descendentes não precisam fornecer "novo código interessante", você pode alterar o abstrato para virtual e fornecer uma implementação de classe base vazia dessa funcionalidade "inserida".

JMD
fonte
2

Seu compilador pode ter um aviso de que pode ser gerado se uma função de classe base ficar oculta. Em caso afirmativo, ative-o. Isso detectará conflitos constantes e diferenças nas listas de parâmetros. Infelizmente, isso não revelará um erro de ortografia.

Por exemplo, este é o aviso C4263 no Microsoft Visual C ++.

Mark Ransom
fonte
1

A overridepalavra-chave C ++ 11, quando usada com a declaração de função dentro da classe derivada, força o compilador a verificar se a função declarada está realmente substituindo alguma função da classe base. Caso contrário, o compilador gerará um erro.

Portanto, você pode usar o overrideespecificador para garantir o polimorfismo dinâmico (substituição de função).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Adarsh ​​Kumar
fonte