Significado de = excluir após a declaração da função

241
class my_class
{
    ...
    my_class(my_class const &) = delete;
    ...
};

O que = deletesignifica nesse contexto?

Existem outros "modificadores" (exceto = 0e = delete)?

Pat O'Keefe
fonte
23
@ Blindy Será padrão em C ++ 0x, ou seja, em breve.
Konrad Rudolph
1
Eu estou corrigido, eu tinha perdido esse recurso C ++ 0x. Eu estava pensando que era um #definea la Qt que foi avaliado como 0 e depois declarou uma função oculta ou algo assim.
Blindy
Tenho uma lembrança de uma palavra-chave 'desativar', que significa o mesmo ou algo semelhante. Estou imaginando isso? Ou existe uma diferença sutil entre eles?
Stewart

Respostas:

201

A exclusão de uma função é um recurso do C ++ 11 :

O idioma comum de "proibir a cópia" agora pode ser expresso diretamente:

class X {
    // ...
    X& operator=(const X&) = delete;  // Disallow copying
    X(const X&) = delete;
};

[...]

O mecanismo "excluir" pode ser usado para qualquer função. Por exemplo, podemos eliminar uma conversão indesejada como esta:

struct Z {
    // ...

    Z(long long);     // can initialize with an long long         
    Z(long) = delete; // but not anything less
};
Prasoon Saurav
fonte
3
O método tradicional de "proibir a cópia" não é apenas para tornar o copiador e o operador = "privados"? Isso vai um pouco mais longe e instrui o compilador a nem gerar as funções. Se ambos são privados e = delete, a cópia é duplamente proibida?
precisa saber é o seguinte
8
@Reb, =deletetorna o método inacessível mesmo em contextos que podem ver privatemétodos (ou seja, dentro da classe e de seus amigos). Isso remove qualquer incerteza quando você está lendo o código. @Paroon, esse segundo exemplo ainda está excluindo apenas os construtores - seria bom ver um excluído, operator long ()por exemplo.
perfil completo de Toby Speight
2
@ Reb.Cabin Using = deleteé melhor do que usar privateou outros mecanismos similares, porque geralmente você deseja que a função proibida seja visivelmente declarada e considerada para resolução de sobrecarga, etc., para que ela possa falhar o mais cedo possível e fornecer o erro mais claro ao usuário. Qualquer solução que envolva "ocultar" a declaração reduz esse efeito.
Leushenko
1
Existe um motivo especial para tornar público o construtor de cópias e aplicar a palavra-chave delete. Por que não deixar o construtor privado e aplicar a palavra-chave?
Dohn Joe
81
  1. = 0significa que uma função é virtual pura e você não pode instanciar um objeto dessa classe. Você precisa derivar dele e implementar esse método
  2. = deletesignifica que o compilador não irá gerar esses construtores para você. AFAIK, isso é permitido apenas no construtor de cópias e no operador de atribuição. Mas não sou muito bom no próximo padrão.
mkaes
fonte
4
Existem outros usos da =deletesintaxe. Por exemplo, você pode usá-lo para proibir explicitamente algum tipo de conversão implícita que pode ocorrer com a chamada. Para isso, você apenas exclui as funções sobrecarregadas. Dê uma olhada na página da Wikipedia em C ++ 0x para obter mais informações.
LiKao
Farei isso assim que encontrar algum. Acho que ele tempo de apanhar com c ++ 0X
mkaes
Sim, C ++ 0x rochas. Mal posso esperar para que o GCC 4.5+ seja mais comum, para começar a usar lambdas.
LiKao 01/04
5
A descrição para = deletenão está totalmente correta. = deletepode ser usado para qualquer função; nesse caso, é explicitamente marcado como excluído e qualquer uso resulta em um erro do compilador. Para funções-membro especiais, isso também significa, em particular, que elas não são geradas para você pelo compilador, mas isso é apenas o resultado de serem excluídas, e não o que = deleterealmente é.
MicroVirus 04/10
28

Este trecho do livro The C ++ Programming Language [4th Edition] - Bjarne Stroustrup fala sobre o real objetivo por trás do uso de =delete:

3.3.4 Suprimindo operações

Usar a cópia padrão ou mover para uma classe em uma hierarquia geralmente é um desastre : dado apenas um ponteiro para uma base, simplesmente não sabemos quais membros a classe derivada possui, portanto, não sabemos como copiá-los . Portanto, a melhor coisa a fazer é excluir as operações padrão de copiar e mover, ou seja, eliminar as definições padrão dessas duas operações:

class Shape {
public:
  Shape(const Shape&) =delete; // no copy operations
  Shape& operator=(const Shape&) =delete;

  Shape(Shape&&) =delete; // no move operations
  Shape& operator=(Shape&&) =delete;
  ˜Shape();
    // ...
};

Agora, uma tentativa de copiar um Shape será capturada pelo compilador.

O =deletemecanismo é geral, ou seja, pode ser usado para suprimir qualquer operação

Saurav Sahu
fonte
5

Os padrões de codificação com os quais trabalhei tiveram o seguinte na maioria das declarações de classe.

//  coding standard: disallow when not used
T(void)                  = delete; // default ctor    (1)
~T(void)                 = delete; // default dtor    (2)
T(const T&)              = delete; // copy ctor       (3)
T(const T&&)             = delete; // move ctor       (4)
T& operator= (const T&)  = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)

Se você usar qualquer um desses 6, basta comentar a linha correspondente.

Exemplo: a classe FizzBus requer apenas dtor e, portanto, não usa os outros 5.

//  coding standard: disallow when not used
FizzBuzz(void)                         = delete; // default ctor (1)
// ~FizzBuzz(void);                              // dtor         (2)
FizzBuzz(const FizzBuzz&)              = delete; // copy ctor    (3)
FizzBuzz& operator= (const FizzBuzz&)  = delete; // copy assig   (4)
FizzBuzz(const FizzBuzz&&)             = delete; // move ctor    (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign  (6)

Comentamos apenas 1 aqui e instalamos a implementação em outro local (provavelmente onde o padrão de codificação sugere). Os outros 5 (de 6) não são permitidos com exclusão.

Você também pode usar '= delete' para não permitir promoções implícitas de valores de tamanhos diferentes ... exemplo

// disallow implicit promotions 
template <class T> operator T(void)              = delete;
template <class T> Vuint64& operator=  (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;
2785528
fonte
3

= deleteé um recurso introduzido no C ++ 11. Conforme =deletenão será permitido chamar essa função.

Em detalhe.

Suponha em uma classe.

Class ABC{
 Int d;
 Public:
  ABC& operator= (const ABC& obj) =delete
  {

  }
};

Ao chamar esta função para atribuição de obj, não será permitido. Significa que o operador de atribuição restringirá a cópia de um objeto para outro.

ashutosh
fonte
2

Novo padrão C ++ 0x. Consulte a seção 8.4.3 no rascunho de trabalho do N3242

dubnde
fonte
Whoa, esse rascunho está desatualizado. Aqui está o mais recente (a partir de 3 de abril de 2011): open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
TonyK
Obrigado e atualizou o link. Muito útil para obter o rascunho atual. A seção / conteúdo referenciado estava correto, mesmo no antigo rascunho, então não entendo o voto negativo.
dubnde
1

Uma função excluída está implicitamente embutida

(Adenda às respostas existentes)

... E uma função excluída deve ser a primeira declaração da função (exceto para excluir especializações explícitas de modelos de função - a exclusão deve estar na primeira declaração da especialização), o que significa que você não pode declarar uma função e depois excluí-la, por exemplo, na sua definição local para uma unidade de tradução.

Citando [dcl.fct.def.delete] / 4 :

Uma função excluída está implicitamente embutida. ( Nota: a regra de definição única ( [basic.def.odr] ) se aplica a definições excluídas. - end note ] Uma definição excluída de uma função deve ser a primeira declaração da função ou, para uma especialização explícita de um modelo de função. , a primeira declaração dessa especialização. [Exemplo:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

- exemplo final )

Um modelo de função principal com uma definição excluída pode ser especializado

Embora uma regra geral geral seja evitar a especialização de modelos de função, uma vez que as especializações não participam da primeira etapa da resolução de sobrecarga, existem alguns contextos discutíveis em que isso pode ser útil. Por exemplo, ao usar um modelo de função principal não sobrecarregado sem definição para corresponder a todos os tipos que não seria implicitamente convertido em uma sobrecarga correspondente por conversão; ou seja, remover implicitamente várias correspondências implícitas de conversão implementando apenas correspondências exatas de tipo na especialização explícita do modelo de função principal não definido e não sobrecarregado.

Antes do conceito de função excluída do C ++ 11, era possível simplesmente omitir a definição do modelo de função principal, mas isso gerava erros de referência indefinidos obscuros que, sem dúvida, não deram nenhuma intenção semântica ao autor do modelo de função primária (omitido intencionalmente) ?). Se, em vez disso, excluirmos explicitamente o modelo de função principal, as mensagens de erro, caso não seja encontrada nenhuma especialização explícita adequada, ficarão muito mais agradáveis ​​e também mostrarão que a omissão / exclusão da definição do modelo de função principal foi intencional.

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t);

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    //use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}

No entanto, em vez de simplesmente omitir uma definição para o modelo de função principal acima, produzindo um erro de referência indefinido obscuro quando nenhuma especialização explícita corresponder, a definição do modelo primário pode ser excluída:

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t) = delete;

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    use_only_explicit_specializations(str);
    /* error: call to deleted function 'use_only_explicit_specializations' 
       note: candidate function [with T = std::__1::basic_string<char>] has 
       been explicitly deleted
       void use_only_explicit_specializations(T t) = delete; */
}

Apresentando uma mensagem de erro mais legível, em que a intenção de exclusão também é claramente visível (onde um erro de referência indefinido pode levar o desenvolvedor a pensar que isso é um erro imprudente).

Voltando ao motivo de querermos usar essa técnica? Mais uma vez, especializações explícitas poderiam ser úteis para implicitamente remover conversões implícitas.

#include <cstdint>
#include <iostream>

void warning_at_best(int8_t num) { 
    std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}

template< typename T >
void only_for_signed(T t) = delete;

template<>
void only_for_signed<int8_t>(int8_t t) {
    std::cout << "UB safe! 1 byte, " << +t << "\n";
}

template<>
void only_for_signed<int16_t>(int16_t t) {
    std::cout << "UB safe! 2 bytes, " << +t << "\n";
}

int main()
{
    const int8_t a = 42;
    const uint8_t b = 255U;
    const int16_t c = 255;
    const float d = 200.F;

    warning_at_best(a); // 42
    warning_at_best(b); // implementation-defined behaviour, no diagnostic required
    warning_at_best(c); // narrowing, -Wconstant-conversion warning
    warning_at_best(d); // undefined behaviour!

    only_for_signed(a);
    only_for_signed(c);

    //only_for_signed(b);  
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = unsigned char] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */

    //only_for_signed(d);
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = float] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */
}
dfri
fonte
0

Isso é algo novo nos padrões C ++ 0x, onde você pode excluir uma função herdada.

Tayyab
fonte
11
Você pode excluir qualquer função. Por exemplo, void foo(int); template <class T> void foo(T) = delete;interrompe todas as conversões implícitas. Somente argumentos do inttipo são aceitos, todos os outros tentarão instanciar uma função "excluída".
UncleBens