Construtores em C++

Inicialização de Membros de Classe

Métodos:

  1. Através da lista de parâmetros do construtor.
  2. Atribuição direta no corpo do construtor.
//1. Inicialização via lista de parâmetros do construtor
Produto::Produto(const Produto &p)
{
	this->codigo = p.codigo;
	this->preco = p.preco;
	this->quantidade = p.quantidade;
}

No entanto, não todos os membros podem ser inicializados por atribuição no corpo do construtor. Membros do tipo const ou referência exigem inicialização através de lista de inicailização.

//2. Inicialização no corpo do construtor
class ConstRef
{
public:
	ConstRef(int ii) : ci(ii), ri(ii)
	{
		i = ii;
	}
private:
	int i;
	const int ci;
	int &ri;
}

Lista de Inicialização de Construtores

A lista de inicialização de construtores é um mecanismo para inicializar variáveis de membro de uma classe. Ela permite a inicialização antes da execução do corpo do construtor, de forma mais eficiente. Este método é particularmente adequado para inicializar variáveis de membro não estáticos e objetos de classes base.

**Sintaxe básica:**A lista de inicialização segue a lista de parâmetros do construtor, introduzida por dois-pontos e separada por vírgulas. Cada inicialização especifica a variável de membro ou objeto de clasce base e sua expressão de inicialização.

Classe::Classe(parâmetros) : membro1(expressão), membro2(expressão) {}
func(int &ref): _ref(ref){}
//Define uma variável de referência ref, atribuindo-a a _ref

Construtores Delegados

Recurso introduzido no C++11 que permite que um construtor chame outro construtor da mesma classe para realizar a inicialização. Este mecanismo evita a repetição de código, especialmente quando múltiplos construitores compartilham tarefas de inicialização comuns.

Sintaxe básica:

class MinhaClasse {
public:
    // Construtor A
    MinhaClasse(int a) : membroA(a) {
        // Código de inicialização A
    }
    // Construtor B, delega para o construtor A
    MinhaClasse() : MinhaClasse(0) {
        // Código de inicialização B
    }
private:
    int membroA;
};

Neste exemplo, o construtor MinhaClasse() (construtor B) delega para MinhaClasse(int a) (construtor A), passando o valor padrão 0. Isso significa que ao criar um objeto usando o construtor B, ele primeiro chama o construtor A para completar parte da inicialização, depois executa seu próprio código de inicialização.

Fluxo de delegação

  1. Chamada de delegação: O construtor delegado chama diretamente outro construtor na lista de inicialização.
  2. Execução sequencial: O construtor delegado executa sua lista de inicialização e código do corpo primeiro.
  3. Continuação da execução: O controle retorna ao construtor delegado, que continua executando seu código do corpo.

Casos de usoConstrutores delegados são adequados para os seguintes cenários:

  • Evitar repetição de código: Quando múltiplos construtores precisam executar o mesmo código de inicialização.
  • Simplificar código: Simplificar construtores complexos, melhorando legibilidade e manutenção.
  • Inicialização hierárquica: Completar a construção do objeto em etapas e níveis de inicialização diferentes.

Considerações

  • Evitar delegação circular: Construtores não podem se delegar infinitamente, resultando em erro de compilação.
  • Delegação para outros construtores: Um construtor pode delegar para qualquer outro construtor definido na classe, incluindo construtores privados.

Construtores de Conversão

São construtores especiais que permitem converter implicitamente ou explicitamente um objeto de outro tipo para o objeto da classe atual. São particularmente úteis na implementação de conversões de tipo, aumentando a flexibilidade e legibilidade do código.

Forma básica:

class MinhaClasse {
public:
    // Construtor de conversão
    MinhaClasse(const OutraClasse& outro) {
        // Lógica de conversão
    }
};

Aqui, o construtor da classe MinhaClasse recebe uma referência constante para um objeto do tipo OutraClasse. Isso significa que ao tentar inicializar um objeto MinhaClasse com um objeto OutraClasse ou atribuir um objeto OutraClasse a um objeto MinhaClasse, este construtor de conversão será chamado automaticamente.

Casos de uso:

  • Conversão de tipo implícita: Quando é necessário converter um tipo implicitamente para outro. Por exemplo: converter uma classe de tempo personalizada para um tipo int representando timestamp.
  • Conversão de tipo explícita: Usando a palavra-chave explicit para prevenir conversões implícitas, aumentando a segurança do código.

Exemplo:

// Classe de temperatura em Celsius
class Celsius
{
public:
	Celsius(double temp) : temperatura(temp){}
	double getTemperatura() const
	{
		return temperatura;
	}
private:
	double temperatura;
};

// Classe de temperatura em Fahrenheit
class Fahrenheit
{
public:
	// Construtor de conversão
	Fahrenheit(const Celsius& c) : temperatura(c.getTemperatura() * 1.8 + 32.0){}
private:
	double temperatura;
};

int main()
{
	Celsius c(25.0);	//c é 25 graus Celsius
	Fahrenheit f = c;	//Conversão implícita, 25°C para 77°F
	return 0;
}

Palavra-chave explicit

Usada para modificar um construtor de classe, impedindo que ele seja usado em conversões de tipo implícitas. Ou seja: usar a palavra-chave explicit força que o construtor só possa ser usado em conversões de tipo explícitas.

Sintaxe básica:

class MinhaClasse
{
public:
	// Construtor explícito
	explicit MinhaClasse(int valor) : dados(valor) {}
private:
	int dados;
}

Classes Agregadas

Classes que permitem acesso direto aos seus membros e possuem uma forma especial de sintaxe de inicialização. Uma classe é considerada agregada quando satisfaz as seguintes condições:

  1. Todos os membros são públicos;
  2. Nenhum construtor é definido;
  3. Não há valores iniciais na classe;
  4. Não há classes base nem funções virtuais.

Exemplo de classe agregada:

struct Dados
{
	int valor;
	string descricao;
}

Pode-se inicializar uma classe agregada com {}:

Dados val2 = {0, "descricao"};

A ordem dos valores iniciais deve corresponder à ordem da declaração. O número de elementos na lista de inicialização não pode exceder o número de membros da classe.

Palavra-chave constexpr

Introduzida no C++11, a palavra-chave constexpr permite que expressões sejam calculadas em tempo de compilação. Aplica-se não apenas a variáveis e funções, mas também a construtores. Construtores constexpr podem ser usados para criar objetos constexpr, inicializados em tempo de compilação e utilizáveis em contextos que exigem expressões constantes.

Condições para um construtor constexpr Para que um construtor seja constexpr, ele deve satisfazer as seguintes condições:

  1. Tipo de retorno: Construtores não têm tipo de retorno (retornam o objeto em construção).
  2. Corpo constexpr: O corpo do construtor deve ser constexpr, contendo apenas a lista de inicialização e uma instrução return (ou sem return, pois construtores retornam implicitamente o objeto em construção).
  3. Parâmetros constexpr: Todos os parâmetros devem ser calculáveis como constexpr.
  4. Inicialização de membros: Todas as variáveis de membro devem ser inicializadas através da lista de inicialização do construtor.

Exemplo:

class Ponto2D {
public:
    constexpr Ponto2D(double xVal = 0, double yVal = 0) : x(xVal), y(yVal) {}
    constexpr double distancia() const { return sqrt(x * x + y * y); }
private:
    double x, y;
};
constexpr Ponto2D pontoGlobal(3.0, 4.0); // Inicialização em tempo de compilação
int main() {
    constexpr Ponto2D pontoLocal(5.0, 12.0); // Inicialização em tempo de compilação
    constexpr double comprimento = pontoLocal.distancia(); // Cálculo em tempo de compilação
    return 0;
}

Neste exemplo, o construtor de Ponto2D é declarado como constexpr, permitindo a criação de objetos constexpr em tempo de compilação (como pontoGlobal e pontoLocal). Além disso, a função distancia também é constexpr, permitindo o cálculo da distância do vetor em tempo de compilação.

Casos de uso

  • Constantes de tempo de compilação: Quando é necessário criar e usar objetos constantes em tempo de compilação.
  • Programação de metamodelos: Em programação de metamodelos, construtores constexpr permitem executar operações mais complexas em tempo de compilação, como cálculo de tamanho de array ou operações matemáticas.
  • Consistência de interface: No desenvolvimento de bibliotecas ou frameworks, fornecer construtores constexpr pode melhorar a consistência da interface, permitindo que usuários usem sua classe em contextos que exigem expressões constantes.

Tags: cpp constructors cpp11 aggregate-class constexpr

Publicado em 6-9 18:44 por Thomas