Ir ao conteúdo
  • Cadastre-se

C++ Diferença entre implementações de construtores


AdrianoSiqueira
Ir à solução Resolvido por arfneto,

Posts recomendados

Estou com uma dúvida sobre construtores, meu IDE (CLion) por padrão cria eles assim:

Pessoa::Pessoa(int idade) : idade(idade) {}

 

Mas se eu fizer assim também funciona:

Pessoa::Pessoa(int idade) {
    this->idade = idade;
}

 

O arquivo Pessoa.h está assim:

class Pessoa {
public:
    int idade;

    Pessoa(int idade);
};

 

Queria saber a diferença (se é que há alguma) entre as implementações dos construtores.

 

Desde já obrigado.

Link para o comentário
Compartilhar em outros sites

  • Solução
2 horas atrás, AdrianoSiqueira disse:

Pessoa::Pessoa(int idade) : idade(idade) {}

 

Em muitas classes os construtores são como formulários apenas e então essa notação é muito mais compacta e legível, e não existe ambiguidade, como você mesmo mostrou: então não precisa escrever tanto. Menos chance  de errar, menos this. menos java :) 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Em 26/11/2020 às 23:50, AdrianoSiqueira disse:

Queria saber a diferença (se é que há alguma)

A diferença é algo mias que simples ser "compacta e legível". Imagine o caso de você herdar de uma classe e precisar que os membros herdados tenham um valor antes mesmo que os proprios membros da sua classe... Para isso existe lista de inicialização. O outro é uma simples atribuição. E não só isso...

Imagine o caso que você tenha uma constante como membro da sua classe... você acha que será capaz dar um valor inicial fazendo uma atribuição dentro do construtor? Somente se pode fazer isso na lista de inicialização. Se poderia dizer que são 2 coisas diferentes... construir e inicializar. Antes da chave de abertura do construtor é construção e depois da chave é atribuição.

Imagine um caso no qual você tem um vehiculo e dentro tem o numero de rodas que seria uma constante... algo como isso:

class vehiculo{

    unsigned const int nRodas;
};


você acha que poderia dar um valor assim?

class vehiculo{

    vehiculo(unsigned const int n)
    {
        this->nRodas = n;
    }


    unsigned const int nRodas;
};

 

A unica forma de fazer isso é dando o valor na hora de "construir" a variável. Digo construir, mas é na hora de que a variável "ganha a memória", porque todos sabemos que dita variável constante n poderá tomar valor posteriormente.

Si usamos listas de inicialização você sim poderá dar valor a dita variável:

class vehiculo{

    vehiculo(unsigned const int n): nRodas{n} {}


    unsigned const int nRodas;
};


Então se poderia dizer que

nomeConstrutor(atributos ou não) construção { atribuição }

 

Depois da chave de abertura como podemos ver, se trata de atribuição. É como se a variável ja existisse e estivéssemos tentando dar um valor, coisa que com constantes n é possivel.


Então meu caro @AdrianoSiqueira não é simples "estética", é algo mais complicado, porém n tanto.

Se alguma vez se der o caso de que você por exemplo herdar de outra classe e precisar de que dita classe tenha que tomar certos valores antes de que os próprios membros, lista de inicialização é a melhor forma, porque uma vez que você passar da chave de abertura tudo já vai estar na memória e dai talvez seu programa seja capaz de n se comportar como você deseja.
 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Em 28/11/2020 às 22:56, vangodp disse:

A diferença é algo mias que simples ser "compacta e legível"....

 

No contexto da pergunta acho que é exatamente isso: uma conveniência: mais compacto e legivel.

Isso desde que você não use duas vezes na inicialização e no corpo a mesma variável porque o compilador não te avisa. E na hora de ler um programa enorme de terceiros você preste atenção para ver se "a outra empresa" não fez isso.  Muitas classes tem vários construtores com longas listas de atribuições e separar essas atribuições da lógica ajuda.


Tem gente que acha o contrário e tem lugares que nem aceitam.

 

Em 28/11/2020 às 22:56, vangodp disse:

Imagine o caso de você herdar de uma classe e precisar que os membros herdados tenham um valor antes mesmo que os proprios membros da sua classe

 

Não é bem assim: o construtor padrão da classe base VAI SER CHAMADO ANTES de qualquer maneira. É a linguagem. Não é que você precise de nada. Vai acontecer. E não precisa dessa lista para isso. Não sei como se escreve isso na literatura em português mas talvez esteja confundindo esse conceito com delegated constructors que é outro animal. Vou mostrar um exemplo completo ao final.

 

A questão levantada pelo autor @AdrianoSiqueira ,
 

image.png.8ea79f9f094f9b0f2f062acb4a6df833.pngE que está aqui ao lado, se diz à expansão opcional de construtores pelo IDE, como pode ser feito também para getters e setters e nada tem a ver com o que @vangodp mencionou.

 

Mas tem um aspecto que eu acho importante nisso, e não tem a ver exatamente com a pergunta do autor e com o que eu respondi antes. Vou explicar aqui e deixar um programa completo de exemplo porque não tem muitas referências sobre isso e pode ser útil a partir desse tópico.

 

Essa notação subclasse() : classe() e a aplicação do operador ":" nesse caso, e quem repito, nada tem a ver com a questão presente, permite que se escolha QUAL o construtor da classe Base será usado para construir uma dada instância da classe herdada. E isso sim é muito importante. 
 

Num passado remoto quando se tinha uma situação assim, e é relativamente comum em classes herdades e hierarquias complexas, sempre se escrevia um método init() e era o que resolvia com os parâmetros adequados esse tipo de problema. Pós-2011 isso passou a ter outra possibilidade com o ":" e os tais delegated construcors.

 

Em 28/11/2020 às 22:56, vangodp disse:

Imagine o caso que você tenha uma constante como membro da sua classe... você acha que será capaz dar um valor inicial fazendo uma atribuição dentro do construtor? Somente se pode fazer isso na lista de inicialização

 

Nunca senti falta disso. Em C++ os caminhos comuns são usar duas outras coisas:

 

Opção 1:
 

#define como em C --- e é o que temos por exemplo para todas as constantes em limits.h. Bem mais comum que criar uma constante em TODAS as instâncias da classe.


O que é M_PI em C++?

 

    #define M_LN10     2.30258509299404568402   // ln(10)
    #define M_PI       3.14159265358979323846   // pi
    #define M_PI_2     1.57079632679489661923   // pi/2
    #define M_PI_4     0.785398163397448309616  // pi/4

      // em cmath

 

E INT_MAX?
 

image.png.f86e99cc393be0ecf5f5caea197118d6.png

 

Pois é. #define é o comum. E está por toda a parte.

 

C++ 2020

 

Ok, C++ 2020 tem o valor de PI em numbers e você pode finalmente escrever numbers::pi. E não é um #define
 

    inline constexpr double pi         = pi_v<double>;

 

Opção 2:
 

namespace coisas
{
    const int um = 1;
    double pi = std::numbers::pi;
    int i = UINT_MAX;
}

 

Sim, um namespace. Em C++ não tem static class para isso, como em java

 

Mas claro que pode usar essa terceira opção que citou. Estranho, mas possível. Em geral há uma convenção com os #define e nem vou dizer qual é. E o namespace tem a origem através do operador "::" como em
 

    std::cout << "a constante " << coisas::um << endl;

 

E é legal ter o namespace e a variável sem precisar de uma instância da classe: @vangodp, note que

  • O namespace evita a colisão com outras classes
  • uma classe que tenha uma constante vai ter isso em toda instância da classe e não faz muito sentido
  • um especificador private para a variável também é uma opção simples
  • o nome do namespace já mostra a origem
     
Em 28/11/2020 às 22:56, vangodp disse:

Si usamos listas de inicialização você sim poderá dar valor a dita variável

 

Mas não é preciso. E no caso de construção de classe base não é essa a razão de usar ":"

E tem uma ambiguidade curiosa aqui: C++ tem um header, 
 

#include <initializer_list>

 

E isso é o que deveriam ser as listas de inicialização em C++. Não é muito usado mas é algo bem versátil. E uma lista de inicialização funciona em qualquer lista de argumentos.

 

Um Exemplo disso tudo: 

 

Vou usar a classe vehiculo que postou, mas algo completo. 
 

Convenções e o exemplo: 

  • Eu uso uma convenção comum, e todas as classes tem a primeira letra em Maiúscula. java-like :) 

Eis a classe revisada ;) 
 

class Vehiculo
{
protected:
    string  nome;
    vector<string> tag;
public:
    Vehiculo() = delete;
    Vehiculo(
        int r,
        initializer_list<string> tags)
    { 
        num_rodas = r;
        for (auto t : tags) tag.push_back(t);
    };
public:
    friend ostream& operator<<(ostream&,Vehiculo&);
private:
    int num_rodas;
};

 

Apenas para demonstração cada Vehiculo tem um nome e um vetor de tags. E tem uma variável com o número de rodas.

O construtor padrão foi invalidado e a única maneira de criar um Vehiculo é usar o construtor com dois parâmetros: 

  • um int com o número de rodas
  • uma lista de tags usando uma lista de inicialização. É um exemplo afinal. Não serve para nada além disso.

Na prática uma classe dessas está em metade dos livros de programação OOP. E em geral a classe veículo é ABSTRATA. Não há razão para se criar um veículo: é a classe base para os tipos de veículo do exemplo. Não vou fazer isso aqui porque iria longe demais e estou sem paciência: bastaria definir uma função virtual pura aqui. No popular, função igual a zero.

 

As classes derivadas
 

class Moto : public Vehiculo
{
public:
    Moto(string n, initializer_list<string> tags) :
        Vehiculo(2, tags) { nome = n; };
};

class Triciclo : public Vehiculo
{
public:
    Triciclo(string n, initializer_list<string> tags) :
        Vehiculo(3, tags) { nome = n; };
};

class Truck : public Vehiculo
{
public:
    Truck(string n, initializer_list<string> tags) :
        Vehiculo( 18, tags ) { nome = n; };
};

 

Aqui dá pra entender o uso do operador ":" na situação de que falei e que não é a que o autor @AdrianoSiqueira postou. E nem a que @vangodp apresentou:
 

O operador ":" é usado para definir qual o construtor de Vehiculo a ser usado para uma certa instância. Poderiam ser muito diferentes, apenas deixei num mínimo. O construtor define o número de rodas e passa uma lista de tags que será carregada no vetor da classe Base. E se cria Moto, Triciclo ou Truck.

 

Um exemplo de uso:

 

int main(void)
{
    std::cout << "a constante " << coisas::um << endl;
    std::cout << "via define: " << _um_ << endl;

    Moto        yamaha(
        "Moto XWE-3434", { "azul", "individual", "economico", "rápido" });
    Triciclo bmw("Triciclo SWE-2323", { "preto", "especial" });
    Truck gigante("18-Wheeler Peterbilt", { "verde", "gigante", "70Ton." });

    cout << yamaha;
    cout << bmw;
    cout << gigante;
};  // main()

 

A saída do exemplo
 

a constante 1
via define: 1

=>Moto XWE-3434 tem 2 rodas
4 Tags: azul individual economico rápido

=>Triciclo SWE-2323 tem 3 rodas
2 Tags: preto especial

=>18-Wheeler Peterbilt tem 18 rodas
3 Tags: verde gigante 70Ton.

 

O exemplo: 
 

#define _um_ 1

#include <initializer_list>
#include <iostream>
#include <vector>

namespace coisas
{
    const int um = 1;
    int i = UINT_MAX;
}

using namespace std;

class Vehiculo
{
protected:
    string  nome;
    vector<string> tag;
public:
    Vehiculo() = delete;
    Vehiculo(
        int r,
        initializer_list<string> tags)
    {
        num_rodas = r;
        for (auto t : tags) tag.push_back(t);
    };
public:
    friend ostream& operator<<(ostream&, Vehiculo&);
private:
    int num_rodas;
};

class Moto : public Vehiculo
{
public:
    Moto(string n, initializer_list<string> tags) :
        Vehiculo(2, tags) {
        nome = n;
    };
};

class Triciclo : public Vehiculo
{
public:
    Triciclo(string n, initializer_list<string> tags) :
        Vehiculo(3, tags) {
        nome = n;
    };
};

class Truck : public Vehiculo
{
public:
    Truck(string n, initializer_list<string> tags) :
        Vehiculo(18, tags) {
        nome = n;
    };
};

int main(void)
{
    std::cout << "a constante " << coisas::um << endl;
    std::cout << "via define: " << _um_ << endl;

    //vehiculo    veiculo; // construtor padrão invalidado
    Moto        yamaha(
        "Moto XWE-3434", { "azul", "individual", "economico", "rápido" });
    Triciclo bmw("Triciclo SWE-2323", { "preto", "especial" });
    Truck gigante("18-Wheeler Peterbilt", { "verde", "gigante", "70Ton." });

    cout << yamaha;
    cout << bmw;
    cout << gigante;
};  // main()

ostream& operator<<(ostream& o, Vehiculo& v)
{
    o << "\n=>" << v.nome << " tem " << v.num_rodas << " rodas\n";
    o << v.tag.size() <<  " Tags: ";
    for (auto t : v.tag) cout << t << " ";
    cout << endl;
    return o;
};  // mostra()

 

 

  • Amei 1
Link para o comentário
Compartilhar em outros sites

1 hora atrás, arfneto disse:

No contexto da pergunta acho que é exatamente isso: uma conveniência: mais compacto e legivel.

Em um suposto caso simples pode até ser. Mas você pecou ao n dizer que sim tem algumas diferenças não é por estética como disse antes. Se o construtor da classe base tiver parâmetros é nesta lista que tem que dar os valores.

 

Link para o comentário
Compartilhar em outros sites

1 hora atrás, vangodp disse:

Em um suposto caso simples pode até ser. Mas você pecou ao n dizer que sim tem algumas diferenças não é por estética como disse antes. Se o construtor da classe base tiver parâmetros é nesta lista que tem que dar os valores

????

 

O caso não é "suposto".  É a pergunta que iniciou o tópico. Veja o título: trata-se da cortesia do IDE CLion de criar um protótipo para os construtores.

 

Não só eu expliquei como mostrei um exemplo usando uma versão operante da classe de 4 linhas sem qualquer código de apoio que você postou. Não entendi...

 

A maneira comum de passar parâmetros para o construtor da classe base é exatamente a que mostrei no programa. delegated constructors. 

 

Está logo acima. Tente ler de novo:

 

class Triciclo : public Vehiculo
{
public:
    Triciclo(string n, initializer_list<string> tags) :
        Vehiculo(3, tags) {
        nome = n;
    };
};

 

 

 

 

Link para o comentário
Compartilhar em outros sites

Para ficar mais claro no exemplo como se usa esse tipo de coisa de especialização de classes:
 

Em casos como o dessa classe Vehiculo em geral não faz sentido que se possa declarar uma instância de Vehiculo. Apenas de classes derivadas, como Moto, Triciclo e Truck no exemplo. Essa é a noção de derivação afinal.

 

Nesses casos se diz que a classe é abstrata. Um protótipo para a geração de classes derivadas. C++ chama as classes de abstratas:
 

image.png.a06347d6fe8f8ab84a941fe2ca1a1058.png

Mas a linguagem curiosamente não tem a qualificação abstract para classes, como tem java.

 

Veja ao lado a mensagem de erro ao tentar declarar um veículo...

 

 

Uma classe em C++ é dita abstrata quando tiver ao menos um método virtual puro. E isso é simples como delcarar uma função = 0 na classe.
 

    virtual unsigned nTags() = 0;

 

Só isso.

 

A partir daí a classe é abstrata e qualquer classe derivada dela que não implementar essa função será também abstrata.

 

Eis o exemplo alterado para implementar Vehiculo como abstrata:

 

#define _um_ 1

#include <initializer_list>
#include <iostream>
#include <vector>

namespace coisas
{
    const int um = 1;
    int i = UINT_MAX;
}

using namespace std;

class Vehiculo
{
protected:
    string  nome;
    vector<string> tag;
public:
    Vehiculo() = delete;
    Vehiculo(
        int r,
        initializer_list<string> tags)
    {
        num_rodas = r;
        for (auto t : tags) tag.push_back(t);
    };
    virtual unsigned nTags() = 0;
public:
    friend ostream& operator<<(ostream&, Vehiculo&);
private:
    int num_rodas;
};

class Moto : public Vehiculo
{
public:
    Moto(string n, initializer_list<string> tags) :
        Vehiculo(2, tags) {
        nome = n;
    };
    unsigned nTags() { return tag.size(); };
};

class Triciclo : public Vehiculo
{
public:
    Triciclo(string n, initializer_list<string> tags) :
        Vehiculo(3, tags) {
        nome = n;
    };
    unsigned nTags() { return tag.size(); };
};

class Truck : public Vehiculo
{
public:
    Truck(string n, initializer_list<string> tags) :
        Vehiculo(18, tags) {
        nome = n;
    };
    unsigned nTags() { return tag.size(); };
};

int main(void)
{
    std::cout << "a constante " << coisas::um << endl;
    std::cout << "via define: " << _um_ << endl;

    Moto        yamaha(
        "Moto XWE-3434", { "azul", "individual", "economico", "rápido" });
    Triciclo bmw("Triciclo SWE-2323", { "preto", "especial" });
    Truck gigante("18-Wheeler Peterbilt", 
        { "verde", "gigante", "70 Toneladas" });

    cout << yamaha;
    cout << bmw;
    cout << gigante;
};  // main()

ostream& operator<<(ostream& o, Vehiculo& v)
{
    o << "\n=>" << v.nome << " tem " << v.num_rodas << " rodas\n";
    o << v.tag.size() <<  " Tags: ";
    for (auto t : v.tag) cout << "'" <<
        t << "' ";
    cout << endl;
    return o;
};  // mostra()

 

 

agora, vangodp disse:

você acha que eu vou ficar aqui escrevendo 20 posts com você? Que lhe diga o dono do tema se ele achar que resolveu ou não a questão. É a ele que você tem que escrever não a mim

 

Não me leve a mal. Não escrevi para você @vangodp Mas agora estou escrevendo para você.

É um forum público e só procurei explicar melhor o que estava no texto e deixar algo mais completo e com mais referências do que o texto que você escreveu. Tem agora mais de 210 visualizações essa questão e outros podem ver sentido no que eu escrevi. 

 

Outros que não você podem aprender algo com as referências e o exemplo. Afinal eu mostrei um exemplo completo

  • com uma classe base e 3 classes derivadas
  • uma classe abstrata e como implementar
  • como declarar uma função virtual pura
  • como redefinir << para simplificar a exibição de classes em ostream
  • como usar namespaces para propagar constantes em C++
  • como declarar e usar o header <initializer_list> para carregar direto um vetor na classe base direto no construtor
  • como usar numbers::pi em C++ 20

Você também pretendia ajudar imagino quando postou a classe vehiculo, embora com menos detalhes:

 

Em 28/11/2020 às 22:56, vangodp disse:

algo como isso:

 

class vehiculo
{
    unsigned const int nRodas;
};

 

 

 

 

 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...