Setters e Getters

1. Setters e Getters

BRUNO WALLISON FERNANDES NUNES
BrunoFN

(usa Ubuntu)

Enviado em 09/07/2015 - 08:42h

Alguém pode me explicar o conceito de Setters e Getters?



  


2. Re: Setters e Getters

Thiago Henrique Hüpner
Thihup

(usa Manjaro Linux)

Enviado em 09/07/2015 - 09:38h

Em geral, seria para modificar/ler um atributo privado.


class Classe{
int x;

public:
void setX(int x){this->x = x;}
int getX(){return this->x;}
};


- Mas pq não usar ele como atributo publico?

Pois assim podemos ter mais controle sobre o codigo, por exemplo, se você quiser alterar o atributo x, mas nao quisse que ele fosse tal numero, na função de "set", poderiamos colocar que não pode ser tal numero e nao alterar a variavel.


class Classe{
int x;

public:
void setX(int x){
if(x!=5){
this->x = x;
}else{
std::cout << "ERRO! Variavel X nao pode ser 5!" << std::endl;
return;
}
}
int getX(){return this->x;}
};


Ou outro exemplo


class Carro{
int numRodas;

public:
void setRodas(int numRodas){
if(numRodas==4){
this->numRodas =numRodas;
}else{
std::cout << "ERRO! Variavel \"numRodas\" nao pode ser diferente de 4!" << std::endl;
return;
}
}
int getRodas(){return this->numRodas;}
};



Espero ter ajudado

Se ajudei, marque o tópico como resolvido e clique em melhor resposta!

[]'s

T+

--
http://piadasnerds.com/wp-content/uploads/2011/08/grafico_souProgramador.png


3. Re: Setters e Getters

M.
XProtoman

(usa Fedora)

Enviado em 09/07/2015 - 11:55h

Cara vou explicar no que isso te ajuda.

Vamos supor que você quer que um atributo tenha valores de 0.0 até 1.0, se você deixasse o atributo público qualquer um poderia alterar o valor e você não garantiria que o valor fosse exatamente entre 0.0 e 1.0. Você no seu SET pode definir que só vai aceitar valores entre essa margem: pronto, perfeito.

Vamos supor que você armazene um valor em FLOAT, porém o seu GET é um inteiro. Ai você diz: Ta certo cara, me da um exemplo prático: um valor de COR, você no SET define como FLOAT e no GET calcula e retorna como INT.

Agora vem: Por que não 2 GETS? Um para valor INT e outro FLOAT. Também você poderia fazer 2 SETS, um para INT e outro para FLOAT, mas tudo salvando numa variável só do tipo FLOAT.

GETs e SETs tem uma vantagem adicional: se a sua variável deixar de existir ou mudar de nome você pode mantê-los por compatibilidade.

Use GETs e SETs no seus projetos que você só tem a ganhar principalmente quando você precisar garantir que apenas um determinado valor ou uma margem seja armazenada(no caso de SETs), quando precisar realizar cálculos para retornar um valor(GETs).

Você se torna tão viciado nisso que até mesmo quando não existe restrição de valor ou qualquer cálculo você acaba preferindo usar para deixar tudo coeso na manipulação de valores dentro de classes.


4. Re: Setters e Getters

Paulo
paulo1205

(usa Ubuntu)

Enviado em 10/07/2015 - 19:52h

A palavra-chave é “encapsulamento”. Isso engloba o que já foi dito antes (poder implementar restrições, esconder detalhes da implementação), mas também envolve a questão de como chegar ao dado.

Quando se pensa em programação orientada a objetos, o objeto deve ser o ponto de acesso para todos os elementos e operações que o compõem. Funções-membros (ou métodos), incluindo getters e setters, são maneiras de garantir que o ponto de entrada a será sempre um objeto.

Abaixo eu mostro um exemplo (muito simplório, até, pois só tem operações triviais) de como o acesso direto a membros de dados pode permitir a destruição do encapsulamento.

class A {                                                                       
public:
int value;
};

int main(){
A *a=new A; // Ponteiro, só para o exemplo ficar mais drástico.
int &r=a->value;
int *p=&a->value;

a->value=5; // Acesso via objeto. OK.
r++; // Altera objeto sem mencioná-lo diretamente (mau!).
(*p)*=2; // Idem (via ponteiro) (mau!).

delete a; // NOTE BEM: Removi o objeto...
r++; // ... mas ainda estou modificando alguma coisa...
(*p)*=2; // ... que nem existe mais! (mau, muito mau!)

{
A aa; // Agora, sem ponteiro.
p=&aa.value; // Mas olha a maldade aqui.
}
// Neste ponto, aa não existe mais, mas p ainda ponta para o que seria parte dele.
(*p)++; // BOOM!
}


A questão do encapsulamento é particularmente relevante com objetos compostos por vários elementos internos, quando a manutenção da coerência entre esses elementos internos é vital para que o objeto seja consistente. Na verdade, classes bem implementadas têm de garantir que todos os objetos estejam completamente consistentes durante todo o seu tempo de vida, desde que ele termina de ser construído até o momento em que será des(cons)truído (e mesmo durante a construção e desconstrução, as coisas também deve ser feitas com ordem, e eventuais erros tratados de forma a manter o estado geral do programa consistente).

Aliás, por falar em construção e desconstrução, o construtor é um setter por excelência, e também um getter do objeto, tomado como um todo.

Veja este outro exemplo de um encapsulador para strings nativas do C com std::string da biblioteca padrão do C++, mas permitindo também representar o equivalente a um ponteiro nulo do C (coisa que std::string não faz, e que é essencialmente diferente de usar simplesmente uma string vazia).

#include <fstream>
#include <iostream>
#include <string>

class c_str_wrapper {
public: /* Deveria ser "private:", mas é para ilustrar o perigo. */
std::string str;
bool is_valid;

public:
c_str_wrapper(const char *s){
if(s){
str=s;
is_valid=true;
}
else
is_valid=false;
}

// Os operadores de conversão de tipos, abaixo, são
// casos particulares de getters, mas, sozinhos, não
// garantem a consistência dos membros internos do
// objeto.
operator bool(){ return is_valid; }

operator const char *() const {
return is_valid? str.c_str(): nullptr;
}
};

bool print(const char *p){
std::cout << (p? (*p? p: "[[EMPTY_STRING]]"): "[[NULL_POINTER]]") << '\n';
return std::cout;
}

int main(){
std::cout.exceptions(std::fstream::failbit|std::fstream::badbit);

c_str_wrapper a{nullptr};
c_str_wrapper b{""};
c_str_wrapper c{"Teste"};

print(a); // Imprime "[[NULL_POINTER]]".
print(b); // Imprime "[[EMPTY_STRING]]".
print(c); // Imprime "Teste".

// Até aqui, tudo OK. A semântica está como esperado. Agora vamos
// começar a esculhambar.

a.is_valid=true;
b.is_valid=false;
c.is_valid=false;

print(a); // Imprime "[[EMPTY_STRING]]" porque casualmente o construtor
// default de std::string coloca um string vazio em a.str,
// mesmo quando ele, pela semântica original, não devesse ser
// usado.
print(b); // Imprime "[[NULL_POINTER]]".
print(c); // Imprime "[[NULL_POINTER]]".

const char *cp_a, *cp_b, *cp_c; // Ponteiros para caracteres constantes.

a.is_valid=b.is_valid=c.is_valid=true; // Todas as strings "válidas".

cp_a=a;
cp_b=b;
cp_c=c;

// Uso const_cast para burlar não só o encapsulamento de c_str_wrapper,
// mas também o de std::string (std::string::c_str() não é naturalmente
// muito segura, mas é algo que se tem de sofrer para poder ter compa-
// tibilidade com strings do C; ela pelo menos retorna um ponteiro para
// constantes, mas casts sempre permitem que a gente transforme qualquer
// coisa em outra).
char *p_a, *p_b, *p_c;

p_a=const_cast<char *>(cp_a);
p_b=const_cast<char *>(cp_b);
p_c=const_cast<char *>(cp_c);

p_a[0]='x'; // Removo o const e sobrescrevo o '\0' do fim da string
// do C usada internamente por std::string.
p_b[0]='y'; // Removo o const e sobrescrevo o '\0'.
p_c[5]='z'; // Idem (só que na sexta posição).

print(a); // Pode imprimir 'x' ou 'y', talvez seguido de lixo, mas pode
// também acontecer qualquer coisa, até capotar o programa.
print(b); // Idem.
print(c); // Pode imprimir "Testez", talvez seguido de lixo, mas pode
// também acontecer qualquer coisa, até capotar o programa.

// Mais "divertido" ainda: mexo em memória fora do espaço alocado pelo
// construtor de std::string para conter textos ou a representação com-
// patível com a do C.
p_a[10]='a';
p_b[10]='b';
p_c[10]='c';

// Mesmo que uma das operações acima não dê pau, pode ser que dê problema
// na hora de chamar um dos destrutores, o que acontece aqui.
}


A classe c_str_wrapper poderia ser melhorada, do seguinte modo.

class c_str_wrapper {
private: /* Não mais "public:". */
std::string str;
bool is_valid;

public:
// Setter que dá ao objeto o sentido de ponteiro nulo.
// Note que ele mexe em mais do que apenas um campo.
void set_null(){
str.clear();
is_valid=false;
}

// Outro setter, que recebe C-string. Note que ele também
// tem de ser esperto.
void set_str(const char *s){
if(s){
str=s;
is_valid=true;
}
else{
str.clear();
is_valid=false;
}
}

// O setter acima é tão adequado ao objeto que o próprio
// construtor de conversão de tipos pode usá-lo.
c_str_wrapper(const char *s){ set_str(s); }

// O operador de conversão de tipo abaixo é um caso
// particular de getter. Uma função equivalente segue.
operator bool() const { return is_valid; }
bool is_null() const { return is_valid; }

// Getter da string, que retorna uma cópia de str ou, se
// o dado não for considerado válido, gera uma exceção
// (note que o tipo de retorno é “std::string”, e não
// “std::string &”).
std::string get_str() const {
if(!is_valid)
throw std::runtime_error("Attempt to get an undefined string");
return str;
}

// O getter especial dado pela conversão de tipos para
// const char * foi removido. Em seu lugar, deve-se usar algo
// como “obj.get_str.c_str()”.

// Outro tipo especial de getter, que coloca uma cópia num
// array do C.
void get_str(char *dst, size_t dst_size) const {
if(!is_valid)
throw std::runtime_error("Attempt to get an undefined string");
if(!dst)
throw std::invalid_argument("Attempt to write to null pointer");
if(dst_size<=str.length())
throw std::range_error("Destination buffer too small");
strcpy(dst, str.c_str());
}
};

// Também seria necessário escrever uma nova versão de print().
bool print(const c_str_wrapper &s){
if(!s) // getter implícito de conversao para bool
std::cout << "[[NULL_POINTER]]";
else{
std::string str(s.get_str());
std::cout << (s.empty()? "[[EMPTY_STRING]]": s);
}
return std::cout << '\n';
}


A eliminação de uma das conversões de tipos é compensada pelos outros getters colocados.

Contudo, só o fato de usar setters e getters não é necessariamente uma garantia do encapsulamento. O tipo de dado devolvido por um getter pode fazer toda a diferença. De modo geral, deve-se evitar fazer getters que devolvam referências ou ponteiros para os membros da classe.

Mas mesmo isso ainda não é tudo. O tipo std::string pode ser implementado com uma semântica de copy-on-write, para fins de economia de memória e também de tempo de execução. Essa facilidade é realmente útil em muitas situações, mas acaba fornecendo potencial combustível para eventuais incendiários. Como dito num comentário no código acima, a função membro c_str() devolve justamente um ponteiro para elemento interno do objeto, e esse elemento é justamente o que é compartilhado no mecanismo de copy-on-write. Então, alguém que deliberadamente use c_str() junto com mecanismos de baixo nível, pode acabar interferindo, de uma vez só, com múltiplos objetos.

int main(){
// Para maior dramatismo, uso o qualificador const para os objetos.
const c_str_wrapper a{"Teste"};
const c_str_wrapper b{a};
const c_str_wrapper c{a};

const_cast<char *>(a.get_str().c_str())[0]='P';

// Corremos o risco de imprimir "Peste" até três vezes
// (e de fato, é o que ocorre na minha máquina)!
print(a);
print(b);
print(c);
}


Esse efeito pode ser contornado por meio de substitutos para o construtor de cópia para o operador de atribuição gerados implicitamente para a classe pelo compilador. Essas versões explícitas devem forçar a geração de um objeto std::string totalmente novo, em lugar de usar a atribuição que usa o recurso de copy-on-write de um objeto string preexistente.

        // Construtor de cópia.
c_str_wrapper(const c_str_wrapper &other) :
str(other.str.c_str()), // Note que eu NÃO fiz “str(other.str)”!!
is_valid(other.is_valid)
{
}

// Operador de atribuição.
c_str_wrapper &operator=(const c_str_wrapper &other){
str=other.str.c_str(); // De novo: eu NÃO fiz “str=other.str”!!
is_valid=other.is_valid;
return *this;
}


Com esses novos membros na classe, o programa anterior mexe apenas com o conteúdo de a.

Há um custo a pagar, no entanto, por essas garantias: um número maior de operações de cópia de strings.

Se eu mexer na versão de c_str_wrapper::get_str() que retorna std::string, trocando o valor retornado de str para str.c_str(), aí já não vou mais conseguir burlar nem o valor de a (e terei mais uma cópia de strings).






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts