Qual é a regra dos três?


2149
  • O que significa copiar um objeto ?
  • O que são o construtor de cópias e o operador de atribuição de cópias ?
  • Quando preciso declará-los eu mesmo?
  • Como posso impedir que meus objetos sejam copiados?

52
Por favor, leia este tópico inteiro e o c++-faqwiki tag antes de votar para fechar .
SBI

13
@ Binário: pelo menos, dedique algum tempo para ler a discussão do comentário antes de votar. O texto costumava ser muito mais simples, mas foi pedido a Fred que o expandisse. Além disso, embora sejam quatro perguntas gramaticalmente , na verdade é apenas uma pergunta com vários aspectos. (Se você não concordar com isso, então provar o seu POV, respondendo a cada uma dessas perguntas sobre o seu próprio e deixe-nos votar sobre os resultados.)
SBI

1
Fred, aqui está uma adição interessante à sua resposta sobre C ++ 1x: stackoverflow.com/questions/4782757/… . Como nós lidamos com isto?
SBI


4
Lembre-se de que, a partir do C ++ 11, acho que isso foi atualizado para a regra dos cinco, ou algo assim.
precisa saber é o seguinte

Respostas:


1795

Introdução

O C ++ trata variáveis ​​de tipos definidos pelo usuário com semântica de valores . Isso significa que os objetos são copiados implicitamente em vários contextos, e devemos entender o que "copiar um objeto" realmente significa.

Vamos considerar um exemplo simples:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(Se você está intrigado com a name(name), age(age)peça, isso é chamado de lista de inicializadores de membros .)

Funções-membro especiais

O que significa copiar um personobjeto? A mainfunção mostra dois cenários distintos de cópia. A inicialização person b(a);é realizada pelo construtor de cópia . Seu trabalho é construir um novo objeto com base no estado de um objeto existente. A atribuição b = aé realizada pelo operador de atribuição de cópia . Seu trabalho geralmente é um pouco mais complicado, porque o objeto de destino já está em algum estado válido que precisa ser tratado.

Como não declaramos nem o construtor de cópias nem o operador de atribuição (nem o destruidor), eles são implicitamente definidos para nós. Citação do padrão:

O construtor de cópias e o operador de atribuição de cópias, [...] e o destruidor são funções-membro especiais. [ Nota : A implementação declarará implicitamente essas funções de membro para alguns tipos de classe quando o programa não as declarar explicitamente. A implementação os definirá implicitamente se eles forem usados. [...] nota final ] [n3126.pdf seção 12 §1]

Por padrão, copiar um objeto significa copiar seus membros:

O construtor de cópia definido implicitamente para uma classe não-união X executa uma cópia de membro de seus subobjetos. [n3126.pdf seção 12.8 §16]

O operador de atribuição de cópia definido implicitamente para uma classe não-união X executa a atribuição de cópia em membros dos seus subobjetos. [n3126.pdf seção 12.8 §30]

Definições implícitas

As funções-membro especiais definidas implicitamente personsão assim:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

Cópia em membros é exatamente o que queremos neste caso: namee agesão copiados, para obter um personobjeto independente e independente . O destruidor definido implicitamente está sempre vazio. Isso também é bom neste caso, pois não adquirimos nenhum recurso no construtor. Os destruidores dos membros são chamados implicitamente após a conclusão do persondestruidor:

Após executar o corpo do destruidor e destruir quaisquer objetos automáticos alocados dentro do corpo, um destruidor da classe X chama os destruidores dos membros diretos de X [n ]126.pdf 12.4 §6]

Gerenciando recursos

Então, quando devemos declarar explicitamente essas funções-membro especiais? Quando nossa classe gerencia um recurso , ou seja, quando um objeto da classe é responsável por esse recurso. Isso geralmente significa que o recurso é adquirido no construtor (ou passado para o construtor) e liberado no destruidor.

Vamos voltar no tempo ao C ++ pré-padrão. Não havia tal coisa std::string, e os programadores estavam apaixonados por indicadores. A personclasse pode ter se parecido com isto:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

Ainda hoje, as pessoas ainda escrevem aulas nesse estilo e se metem em problemas: " Eu empurrei uma pessoa para um vetor e agora tenho erros de memória loucos! " Lembre-se de que, por padrão, copiar um objeto significa copiar seus membros, mas copiar o namemembro apenas copia um ponteiro, não a matriz de caracteres que ele aponta! Isso tem vários efeitos desagradáveis:

  1. Alterações via apodem ser observadas via b.
  2. Uma vez bdestruído, a.nameé um ponteiro pendente.
  3. Se afor destruído, a exclusão do ponteiro danificado gera um comportamento indefinido .
  4. Como a tarefa não leva em consideração o que foi nameapontado antes da tarefa, mais cedo ou mais tarde você terá vazamentos de memória por todo o lugar.

Definições explícitas

Como a cópia em membros não tem o efeito desejado, devemos definir explicitamente o construtor de cópias e o operador de atribuição de cópias para fazer cópias profundas da matriz de caracteres:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

Observe a diferença entre inicialização e designação: precisamos desmembrar o estado antigo antes de designar namepara evitar vazamentos de memória. Além disso, temos que nos proteger contra a atribuição automática do formulário x = x. Sem essa verificação, delete[] nameexcluiria a matriz que contém a cadeia de origem , porque quando você escreve x = x, ambas this->namee that.namecontêm o mesmo ponteiro.

Exceção de segurança

Infelizmente, esta solução falhará se new char[...]lançar uma exceção devido ao esgotamento da memória. Uma solução possível é introduzir uma variável local e reordenar as instruções:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

Isso também cuida da auto-atribuição sem uma verificação explícita. Uma solução ainda mais robusta para esse problema é o idioma de copiar e trocar , mas não abordarei os detalhes de segurança de exceção aqui. Mencionei apenas exceções para enfatizar o seguinte: Escrever aulas que gerenciam recursos é difícil.

Recursos não copiáveis

Alguns recursos não podem ou não devem ser copiados, como identificadores de arquivo ou mutexes. Nesse caso, simplesmente declare o construtor de cópias e o operador de atribuição de cópias como privatesem fornecer uma definição:

private:

    person(const person& that);
    person& operator=(const person& that);

Como alternativa, você pode herdar boost::noncopyableou declarar como excluído (no C ++ 11 e acima):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

A regra de três

Às vezes, você precisa implementar uma classe que gerencia um recurso. (Nunca gerencie vários recursos em uma única classe, isso só causará problemas.) Nesse caso, lembre-se da regra de três :

Se você precisar declarar explicitamente o destruidor, o construtor de cópias ou o operador de atribuição de cópias, provavelmente precisará declarar explicitamente todos os três.

(Infelizmente, essa "regra" não é imposta pelo padrão C ++ ou por qualquer compilador que eu conheça.)

A regra dos cinco

A partir do C ++ 11, um objeto possui 2 funções-membro especiais extras: o construtor de movimentação e a atribuição de movimentação. A regra dos cinco estados para implementar essas funções também.

Um exemplo com as assinaturas:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

A regra do zero

A regra de 3/5 também é conhecida como regra de 0/3/5. A parte zero da regra declara que você tem permissão para não escrever nenhuma das funções especiais de membro ao criar sua classe.

Adendo

Na maioria das vezes, você não precisa gerenciar um recurso por conta própria, porque uma classe existente, como std::stringjá faz isso por você. Basta comparar o código simples usando um std::stringmembro com a alternativa complicada e propensa a erros usando ae char*você deve estar convencido. Desde que você fique longe dos membros brutos do ponteiro, é improvável que a regra dos três diga respeito ao seu próprio código.


4
Fred, eu me sentiria melhor com minha votação antecipada se (A) você não explicasse a atribuição mal implementada no código copiável e adicione uma nota dizendo que está errada e procure outro local na cópia fina; use c & s no código ou apenas pule a implementação de todos esses membros (B). Você reduziria a primeira metade, que tem pouco a ver com o RoT; (C) você discutirá a introdução da semântica de movimentação e o que isso significa para o RoT.
Sbi

7
Mas então o post deve ser feito em C / W, eu acho. Gosto de manter os termos mais precisos (ou seja, você diz " operador de atribuição de cópia " e não entra na armadilha comum de que a atribuição não pode implicar uma cópia).
Johannes Schaub - litb

4
@ Pasoon: Eu não acho que cortar metade da resposta seja visto como "edição justa" de uma resposta que não seja da CW.
SBI

69
Seria ótimo se você atualizar seu post para C ++ 11 (ie movimento construtor / atribuição)
Alexander Malakhov

5
Qualquer coisa @solalito você deve liberar após o uso: fechaduras de simultaneidade, identificadores de arquivo, conexões de banco de dados, soquetes de rede, memória heap ...
fredoverflow

509

A Regra dos Três é uma regra de ouro para C ++, basicamente dizendo

Se a sua turma precisar de algum

  • um construtor de cópias ,
  • um operador de atribuição ,
  • ou um destruidor ,

definido explicitamente, é provável que precise dos três .

A razão para isso é que os três geralmente são usados ​​para gerenciar um recurso e, se sua classe gerencia um recurso, geralmente é necessário gerenciar a cópia e a liberação.

Se não houver uma boa semântica para copiar o recurso que sua classe gerencia, considere proibir a cópia declarando (não definindo ) o construtor de cópia e o operador de atribuição como private.

(Observe que a próxima nova versão do padrão C ++ (que é C ++ 11) adiciona semântica de movimentação ao C ++, o que provavelmente mudará a Regra de Três. No entanto, eu sei muito pouco sobre isso para escrever uma seção do C ++ 11 sobre a Regra dos Três.)


3
Outra solução para impedir a cópia é herdar (em particular) de uma classe que não pode ser copiada (como boost::noncopyable). Também pode ser muito mais claro. Eu acho que C ++ 0x e a possibilidade de "excluir" funções podem ajudar aqui, mas esqueci a sintaxe: /
Matthieu M.

2
@ Matthieu: Sim, isso também funciona. Mas a menos que faça noncopyableparte da lib std, não considero isso uma grande melhoria. (Ah, e se você esqueceu a sintaxe eliminação, você esqueceu mor ethan que já conheci. :))
SBI

3
@ Daan: Veja esta resposta . No entanto, eu recomendaria seguir a Regra do Zero de Martinho . Para mim, essa é uma das regras mais importantes do C ++ cunhadas na última década.
SBI

3
Regra de Martinho de Zero agora melhor (sem aparente aquisição adware), localizada no archive.org
Nathan Kidd

161

A lei dos três grandes é a especificada acima.

Um exemplo fácil, em inglês simples, do tipo de problema que ele resolve:

Destruidor não padrão

Você alocou memória no seu construtor e, portanto, precisa escrever um destruidor para excluí-lo. Caso contrário, você causará um vazamento de memória.

Você pode pensar que isso é trabalho feito.

O problema será que, se uma cópia for feita do seu objeto, a cópia apontará para a mesma memória que o objeto original.

Uma vez, uma delas exclui a memória em seu destruidor, a outra terá um ponteiro para a memória inválida (isso é chamado de ponteiro danificado) quando ela tenta usá-la, as coisas vão ficar peludas.

Portanto, você escreve um construtor de cópias para que ele aloque novos objetos que seus próprios pedaços de memória sejam destruídos.

Operador de atribuição e construtor de cópias

Você alocou memória no construtor para um ponteiro de membro da sua classe. Quando você copia um objeto dessa classe, o operador de atribuição padrão e o construtor de cópias copiam o valor desse ponteiro de membro para o novo objeto.

Isso significa que o novo objeto e o objeto antigo estarão apontando para o mesmo pedaço de memória; portanto, quando você o altera em um objeto, ele também é alterado para o outro objeto. Se um objeto excluir essa memória, o outro continuará tentando usá-la - por exemplo.

Para resolver isso, escreva sua própria versão do construtor de cópias e operador de atribuição. Suas versões alocam memória separada para os novos objetos e copiam os valores que o primeiro ponteiro está apontando, em vez de seu endereço.


4
Portanto, se usarmos um construtor de cópia, a cópia será feita, mas em um local de memória diferente, e se não usarmos o construtor de cópia, a cópia será feita, mas apontará para o mesmo local de memória. é isso que você está tentando dizer? Portanto, uma cópia sem construtor de cópias significa que um novo ponteiro estará lá, mas apontando para o mesmo local de memória; no entanto, se tivermos um construtor de cópias definido explicitamente pelo usuário, teremos um ponteiro separado apontando para um local de memória diferente, mas com os dados.
Inquebrável

4
Desculpe, eu respondi a isso há séculos, mas a minha resposta não parece ainda estar aqui :-( Basicamente, sim - você obtê-lo :-)
Stefan

1
Como o princípio afasta o operador de atribuição de cópias? Essa resposta seria mais útil se a terceira da Regra dos Três fosse mencionada.
DBedrenko

1
@DBedrenko, "você escreve um construtor de cópias para que ele aloque novos objetos com suas próprias peças de memória ..." é o mesmo princípio que se estende ao operador de atribuição de cópias. Você não acha que eu deixei isso claro?
21417 Stefan #

2
@DBedrenko, adicionei mais algumas informações. Isso torna mais claro?
21417 Stefan #

44

Basicamente, se você tem um destruidor (não o destruidor padrão), isso significa que a classe que você definiu possui alguma alocação de memória. Suponha que a classe seja usada fora por algum código de cliente ou por você.

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

Se MyClass tiver apenas alguns membros de tipo primitivo, um operador de atribuição padrão funcionaria, mas se tiver alguns membros e objetos de ponteiro que não possuem operadores de atribuição, o resultado será imprevisível. Portanto, podemos dizer que, se houver algo a ser excluído no destruidor de uma classe, podemos precisar de um operador de cópia profunda, o que significa que devemos fornecer um construtor de cópias e um operador de atribuição.


36

O que significa copiar um objeto? Existem algumas maneiras de copiar objetos - vamos falar sobre os dois tipos aos quais você provavelmente se refere - cópia profunda e cópia superficial.

Como estamos em uma linguagem orientada a objetos (ou pelo menos estamos assumindo isso), digamos que você tenha um pedaço de memória alocado. Como é uma linguagem OO, podemos facilmente nos referir a pedaços de memória que alocamos porque geralmente são variáveis ​​primitivas (ints, chars, bytes) ou classes que definimos que são feitas de nossos próprios tipos e primitivos. Então, digamos que temos uma classe de carro da seguinte maneira:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

Uma cópia profunda é se declararmos um objeto e, em seguida, criarmos uma cópia completamente separada do objeto ... acabaremos com 2 objetos em 2 conjuntos de memória completamente.

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

Agora vamos fazer algo estranho. Digamos que o carro2 seja programado errado ou intencionalmente destinado a compartilhar a memória real da qual o carro1 é feito. (Geralmente, é um erro fazer isso e, nas aulas, geralmente é o cobertor discutido.) Finja que sempre que você pergunta sobre o car2, está realmente resolvendo um ponteiro para o espaço de memória do car1 ... isso é mais ou menos uma cópia superficial é.

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

Portanto, independentemente do idioma em que você está escrevendo, tenha muito cuidado com o que você quer dizer quando se trata de copiar objetos, porque na maioria das vezes você deseja uma cópia profunda.

O que são o construtor de cópias e o operador de atribuição de cópias? Eu já os usei acima. O construtor de cópia é chamado quando você digita um código como Car car2 = car1; Essencialmente, se você declarar uma variável e atribuí-la em uma linha, é quando o construtor de cópia é chamado. O operador de atribuição é o que acontece quando você usa um sinal de igual-- car2 = car1;. O aviso car2não está declarado na mesma declaração. Os dois pedaços de código que você escreve para essas operações provavelmente são muito semelhantes. De fato, o padrão de design típico tem outra função que você chama para definir tudo quando estiver satisfeito com a cópia / atribuição inicial ser legítima - se você olhar para o código longhand que escrevi, as funções são quase idênticas.

Quando preciso declará-los eu mesmo? Se você não está escrevendo um código que deve ser compartilhado ou produzido de alguma maneira, você só precisará declará-lo quando precisar. Você precisa estar ciente do que a linguagem do seu programa faz se optar por usá-la 'por acidente' e não a criou - ou seja, você obtém o padrão do compilador. Eu raramente uso construtores de cópia, por exemplo, mas as substituições do operador de atribuição são muito comuns. Você sabia que pode substituir o que adição, subtração etc. também significam?

Como posso impedir que meus objetos sejam copiados? Substituir todas as maneiras pelas quais você pode alocar memória para seu objeto com uma função privada é um começo razoável. Se você realmente não quer que as pessoas as copiem, você pode torná-lo público e alertar o programador lançando uma exceção e também não copiando o objeto.


5
A pergunta foi marcada com C ++. Essa exposição de pseudo-código pouco faz para esclarecer qualquer coisa sobre a bem-definida "Regra Dos Três" na melhor das hipóteses, e apenas espalha confusão na pior das hipóteses.
precisa

26

Quando preciso declará-los eu mesmo?

A Regra dos Três estabelece que, se você declarar algum

  1. copiar construtor
  2. operador de atribuição de cópia
  3. destruidor

então você deve declarar todos os três. Surgiu da observação de que a necessidade de assumir o significado de uma operação de cópia quase sempre decorria da classe realizar algum tipo de gerenciamento de recursos, e isso quase sempre implicava que

  • qualquer gerenciamento de recursos que estivesse sendo feito em uma operação de cópia provavelmente precisava ser feito na outra operação de cópia e

  • o destruidor de classe também participaria do gerenciamento do recurso (geralmente liberando-o). O recurso clássico a ser gerenciado era a memória, e é por isso que todas as classes da Biblioteca Padrão que gerenciam a memória (por exemplo, os contêineres STL que executam o gerenciamento dinâmico de memória) declaram "os três grandes": operações de cópia e destruidor.

Uma conseqüência da Regra dos Três é que a presença de um destruidor declarado pelo usuário indica que é improvável que uma cópia simples de membro seja apropriada para as operações de cópia na classe. Isso, por sua vez, sugere que, se uma classe declara um destruidor, as operações de cópia provavelmente não devem ser geradas automaticamente, porque elas não fariam a coisa certa. No momento em que o C ++ 98 foi adotado, o significado dessa linha de raciocínio não era totalmente apreciado; portanto, no C ++ 98, a existência de um destruidor declarado pelo usuário não teve impacto na disposição dos compiladores em gerar operações de cópia. Esse continua sendo o caso no C ++ 11, mas somente porque restringir as condições sob as quais as operações de cópia são geradas quebraria muito código legado.

Como posso impedir que meus objetos sejam copiados?

Declarar o construtor de cópias e o operador de atribuição de cópias como especificador de acesso privado.

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

No C ++ 11 em diante, você também pode declarar que o construtor de cópias e o operador de atribuição foram excluídos

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}


10

A regra de três no C ++ é um princípio fundamental do design e do desenvolvimento de três requisitos que, se houver uma definição clara em uma das seguintes funções de membro, o programador deverá definir as outras funções de dois membros juntos. Nomeadamente, as três funções-membro a seguir são indispensáveis: destruidor, construtor de cópias, operador de atribuição de cópias.

O construtor de cópias em C ++ é um construtor especial. É usado para criar um novo objeto, que é o novo objeto equivalente a uma cópia de um objeto existente.

O operador de atribuição de cópia é um operador de atribuição especial que geralmente é usado para especificar um objeto existente para outros do mesmo tipo de objeto.

Existem exemplos rápidos:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;

7
Olá, sua resposta não adiciona nada de novo. Os outros abordam o assunto com muito mais profundidade e precisão - sua resposta é aproximada e, de fato, errada em alguns lugares (a saber, não há "deve" aqui; é "muito provavelmente deveria"). Realmente não valeria a pena postar esse tipo de resposta para perguntas que já foram completamente respondidas. A menos que você tenha coisas novas a adicionar.
Mat

1
Além disso, existem quatro exemplos rápidos, que de alguma forma estão relacionados a dois dos três dos quais a Regra dos Três está falando. Muita confusão.
precisa saber é
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.