Construtores (C++)
Um construtor é uma função de membro especial que inicializa uma instância de sua classe. Para chamar um construtor, você usa o nome da classe junto com parâmetros entre chaves ou parênteses.
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class SomeClass{
public:
static void set_box(const Box& aBox) {}
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
SomeClass::set_box(Box{ 5, 6, 7 });
}
Para obter mais informações, consulte Regras para declarar construtores. Para obter informações sobre inicialização, consulte Inicialização.
Ordem de construção
Um construtor executa seu trabalho nesta ordem:
Chama a classe base e os construtores membros na ordem da declaração.
Se a classe for derivada de classes base virtuais, ela inicializará os ponteiros de base virtuais do objeto.
Se a classe tiver ou herdar funções virtuais, ela inicializará os ponteiros de função virtual do objeto. Os ponteiros de função virtual apontam para a tabela de função virtual da classe para permitir a associação de chamadas de função virtual ao código.
Executa qualquer código no corpo de sua função.
O exemplo a seguir mostra a ordem em que a classe base e os construtores membros são chamados no construtor para uma classe derivada. Primeiro, o construtor de base é chamado, depois os membros da classe base são inicializados na ordem em que aparecem na declaração de classe e, em seguida, o construtor derivado é chamado.
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() {
cout << "Contained1 constructor." << endl;
}
};
class Contained2 {
public:
Contained2() {
cout << "Contained2 constructor." << endl;
}
};
class Contained3 {
public:
Contained3() {
cout << "Contained3 constructor." << endl;
}
};
class BaseContainer {
public:
BaseContainer() {
cout << "BaseContainer constructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
cout << "DerivedContainer constructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
int x = 3;
}
Este é o resultado:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
Se um construtor gerar uma exceção, a ordem de destruição será a inversa da ordem de construção:
O código no corpo da função de construtor é liberado.
Os objetos de classe base e de membros são destruídos, na ordem inversa da declaração.
Se o construtor não for representante, todos os objetos da classe base e membros completamente construídos serão destruídos. No entanto, como o próprio objeto não está totalmente construído, o destruidor não é executado.
Construtores explícitos
Usar a palavra-chave explicit em um construtor pode impedir conversões implícitas de tipo se o construtor tem apenas um parâmetro ou se todos os parâmetros, exceto um, possuem um valor padrão. Para obter mais informações, consulte Construtores (C++).
Construtores padrão
Construtores padrão, ou seja, construtores que não têm parâmetros, seguem regras ligeiramente diferentes.
Se nenhum construtor for declarado em uma classe, o compilador fornecerá um construtor padrão:
class Box {
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1{};
Box box2;
}
Quando você chama um construtor padrão e tenta usar parênteses, é emitido um aviso:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
Este é um exemplo do problema Most Vexing Parse. Como a expressão de exemplo pode ser interpretada como a declaração de uma função ou como invocação de um construtor padrão, e como os analisadores de C++ preferem as declarações a outras coisas, a expressão é tratada como uma declaração de função. Para obter mais informações, consulte Most Vexing Parse.
Se algum construtor não padrão for declarado, o compilador não fornecerá um construtor padrão:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box4; // compiler error C2512: no appropriate default constructor available
}
Se uma classe não tiver um construtor padrão, uma matriz de objetos dessa classe não poderá ser construída usando apenas a sintaxe de colchete. Por exemplo, dado o bloco de códigos anterior, uma matriz de caixas não pode ser declarada assim:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
No entanto, você pode usar um conjunto de listas de inicializadores para inicializar uma matriz de caixas:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Construtores de cópia e movimentação
Um construtor de cópia usa uma referência a um objeto para fazer uma cópia dele. Se você não fornecer um construtor de cópia para uma classe, o compilador criará um padrão, mesmo quando uma operação de cópia, uma operação de movimentação ou um destruidor forem declarados. Para obter mais informações, consulte Regras para declarar construtores.
Um construtor de movimentação permite a transferência da memória alocada de um objeto para outro. Para obter mais informações, consulte Como escrever um construtor move.
Construtores explicitamente usados como padrão e excluídos
Você pode explicitamente padrão construtores padrão, construtores de cópia, operadores de atribuição de cópia e destruidores, mas não há suporte para o padrão explícito de construtores de movimentação e operadores de atribuição de movimentação. (Esse suporte está presente no Visual Studio de 2015.) É possível excluir explicitamente todas as funções especiais. Para obter mais informações, consulte Funções explicitamente usadas como padrão e excluídas.
Construtores em classes derivadas
Um construtor de classe derivada sempre chama um construtor de classe base, de modo que possa confiar em classes base completamente construídas antes que qualquer trabalho adicional seja feito. Os construtores de classe base são chamados por ordem derivação – por exemplo, se ClassA é derivada de ClassB, que é derivada de ClassC, o construtor de ClassC é chamado primeiro, depois o construtor de ClassB e, por fim, o construtor de ClassA.
Se uma classe base não tiver um construtor padrão, você deverá fornecer os parâmetros do construtor de classe base no construtor de classe derivada:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
m_label = label;
}
private:
string m_label;
};
int main(){
const string aLabel = "aLabel";
StorageBox sb(1, 2, 3, aLabel);
}
Construtores para as classes que têm várias heranças
Se uma classe for derivada de várias classes base, os construtores de classe base serão chamados na ordem em que estão listados na declaração da classe derivada:
#include <iostream>
using namespace std;
class BaseClass1 {
public:
BaseClass1() {
cout << "BaseClass1 constructor." << endl;
}
};
class BaseClass2 {
public:
BaseClass2() {
cout << "BaseClass2 constructor." << endl;
}
};
class BaseClass3{
public:
BaseClass3() {
cout << "BaseClass3 constructor." << endl;
}
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 {
public:
DerivedClass() {
cout << "DerivedClass constructor." << endl;
}
};
int main() {
DerivedClass dc;
}
O seguinte resultado é esperado:
BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.
Funções virtuais em construtores
Recomendamos que você seja cuidadoso ao chamar funções virtuais nos construtores. Como o construtor da classe base é sempre chamado antes do construtor de classe derivada, a função que é chamada no construtor de base é a versão da classe base, não a versão da classe derivada. No exemplo a seguir, construir DerivedClass faz com que a implementação de BaseClass de print_it() seja executada antes que o construtor de DerivedClass faça com que a implementação de DerivedClass de print_it() seja executada:
#include <iostream>
using namespace std;
class BaseClass{
public:
BaseClass(){
print_it();
}
virtual void print_it() {
cout << "BaseClass print_it" << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
print_it();
}
virtual void print_it(){
cout << "Derived Class print_it" << endl;
}
};
int main() {
DerivedClass dc;
}
Este é o resultado:
BaseClass print_it
Derived Class print_it
Construtores e classes compostas
As classes que contêm membros do tipo classe são conhecidas como classes compostas. Quando um membro do tipo classe de uma classe composta é criado, o construtor é chamado antes do próprio construtor da classe. Quando uma classe contida não possuir um construtor padrão, você deverá usar uma lista de inicialização no construtor da classe composta. No exemplo anterior de StorageBox, se você alterar o tipo da variável de membro m_label para uma nova classe Label, deverá chamar o construtor da classe base e inicializar a variável m_label no construtor StorageBox:
class Label {
public:
Label(const string& name, const string& address) { m_name = name; m_address = address; }
string m_name;
string m_address;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, Label label)
: Box(width, length, height), m_label(label){}
private:
Label m_label;
};
int main(){
// passing a named Label
Label label1{ "some_name", "some_address" };
StorageBox sb1(1, 2, 3, label1);
// passing a temporary label
StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });
// passing a temporary label as an initializer list
StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}
Construtores de delegação
Um construtor de delegação chama um construtor diferente na mesma classe para fazer algum de trabalho de inicialização. No exemplo a seguir, a classe derivada tem três construtores – o segundo construtor delega o primeiro, e o terceiro construtor delega o segundo:
#include <iostream>
using namespace std;
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
ConstructorDestructor dc(1, 2, 3);
}
Este é o resultado:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.
O objeto criado pelos construtores é inicializado totalmente assim que o construtor é concluído. DerivedContainer(int int1) tem êxito, mas DerivedContainer(int int1, int int2) falha e o destruidor é chamado.
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
try {
ConstructorDestructor cd{ 1, 2, 3 };
}
catch (const exception& ex){
}
}
Saída:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.
Para obter mais informações, consulte Inicialização uniforme e delegação de construtores.