Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Para personalizar como uma classe inicializa seus membros ou para invocar funções quando um objeto de sua classe é criado, defina um construtor. Um construtor tem o mesmo nome que a classe e nenhum valor de retorno. Você pode definir quantos construtores sobrecarregados forem necessários para personalizar a inicialização de várias maneiras. Normalmente, os construtores têm acessibilidade pública para que o código fora da definição de classe ou hierarquia de herança possa criar objetos da classe. Mas você também pode declarar um construtor como protected ou private.
Os construtores podem, opcionalmente, usar uma lista de inicializadores de membros. É uma maneira mais eficiente de inicializar membros da classe do que atribuir valores no corpo do construtor. O exemplo a seguir mostra uma classe Box com três construtores sobrecarregados. Os dois últimos usam listas de inicialização de membros:
class Box {
public:
// Default constructor
Box() {}
// Initialize a Box with equal dimensions (i.e. a cube)
explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
{}
// Initialize a Box with custom dimensions
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height)
{}
int Volume() { return m_width * m_length * m_height; }
private:
// Will have value of 0 when default constructor is called.
// If we didn't zero-init here, default constructor would
// leave them uninitialized with garbage values.
int m_width{ 0 };
int m_length{ 0 };
int m_height{ 0 };
};
Quando você declara uma instância de uma classe, o compilador escolhe qual construtor invocar com base nas regras de resolução de sobrecarga:
int main()
{
Box b; // Calls Box()
// Using uniform initialization (preferred):
Box b2 {5}; // Calls Box(int)
Box b3 {5, 8, 12}; // Calls Box(int, int, int)
// Using function-style notation:
Box b4(2, 4, 6); // Calls Box(int, int, int)
}
- Os construtores podem ser declarados como
inline,explicit,friend, ouconstexpr. - Um construtor pode inicializar um objeto que foi declarado como
const,volatileouconst volatile. O objeto torna-se depois que o construtor éconstconcluído. - Para definir um construtor em um arquivo de implementação, dê-lhe um nome qualificado como qualquer outra função membro:
Box::Box(){...}.
Listas de inicializadores de membros
Um construtor pode opcionalmente ter uma lista de inicializadores de membros, que inicializa os membros da classe antes que o corpo do construtor seja executado. (Uma lista de inicializadores de membros não é a mesma coisa que uma lista de inicializadores do tipo std::initializer_list<T>.)
Prefira listas de inicializadores de membros em vez de atribuir valores no corpo do construtor. Uma lista de inicializadores de membros inicializa diretamente os membros. O exemplo a seguir mostra a lista de inicializadores de membros, que consiste em todas as expressões após os identifier(argument) dois pontos:
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height)
{}
O identificador deve referir-se a um membro da classe; é inicializado com o valor do argumento. O argumento pode ser um dos parâmetros do construtor, uma chamada de função ou um std::initializer_list<T>arquivo .
const Membros e membros do tipo de referência devem ser inicializados na lista Inicializador de Membros.
Para garantir que as classes base sejam totalmente inicializadas antes que o construtor derivado seja executado, chame qualquer construtor de classe base parametrizado na lista de inicializadores.
Construtores padrão
Os construtores padrão normalmente não têm parâmetros, mas podem ter parâmetros com valores padrão.
class Box {
public:
Box() { /*perform any required default initialization steps*/}
// All params have default values
Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}
Construtores padrão são uma das funções de membro especial. Se nenhum construtor for declarado em uma classe, o compilador fornecerá um construtor padrão implícito inline .
#include <iostream>
using namespace std;
class Box {
public:
int Volume() {return m_width * m_height * m_length;}
private:
int m_width { 0 };
int m_height { 0 };
int m_length { 0 };
};
int main() {
Box box1; // Invoke compiler-generated constructor
cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}
Se você confiar em um construtor padrão implícito, certifique-se de inicializar membros na definição de classe, como mostrado no exemplo anterior. Sem esses inicializadores, os membros não seriam inicializados e a chamada Volume() produziria um valor de lixo. Em geral, é uma boa prática inicializar membros dessa maneira, mesmo quando não dependem de um construtor padrão implícito.
Você pode impedir que o compilador gere um construtor padrão implícito definindo-o como excluído:
// Default constructor
Box() = delete;
Um construtor padrão gerado pelo compilador será definido como excluído se algum membro da classe não for construível por padrão. Por exemplo, todos os membros do tipo de classe, e seus membros de tipo de classe, devem ter um construtor padrão e destruidores que sejam acessíveis. Todos os membros de dados do tipo de referência e todos os const membros devem ter um inicializador de membro padrão.
Quando você chama um construtor padrão gerado pelo compilador e tenta usar parênteses, um aviso é emitido:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
Esta afirmação é um exemplo do problema "Most Vexing Parse". Você pode interpretar myclass md(); como uma declaração de função ou como a invocação de um construtor padrão. Como os analisadores C++ favorecem declarações em detrimento de outras coisas, a expressão é tratada como uma declaração de função. Para obter mais informações, consulte Most Vexing Parse.
Se quaisquer construtores não padrão forem declarados, 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 box3; // 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 entre colchetes. Por exemplo, dado o bloco de código anterior, uma matriz de Caixas não pode ser declarada assim:
Box boxes[3]; // C2512: no appropriate default constructor available
No entanto, você pode usar um conjunto de listas de inicializadores para inicializar uma matriz de objetos Box:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Para obter mais informações, consulte Inicializadores.
Construtores de cópia
Um construtor copy inicializa um objeto copiando os valores de membro de um objeto do mesmo tipo. Se os membros da sua classe são todos tipos simples, como valores escalares, o construtor de cópia gerado pelo compilador é suficiente e você não precisa definir o seu próprio. Se sua classe requer inicialização mais complexa, então você precisa implementar um construtor de cópia personalizada. Por exemplo, se um membro da classe for um ponteiro, você precisará definir um construtor de cópia para alocar nova memória e copiar os valores do objeto apontado para o outro. O construtor de cópia gerado pelo compilador simplesmente copia o ponteiro, de modo que o novo ponteiro ainda aponte para o local de memória do outro.
Um construtor de cópia pode ter uma destas assinaturas:
Box(Box& other); // Avoid if possible--allows modification of other.
Box(const Box& other);
Box(volatile Box& other);
Box(volatile const Box& other);
// Additional parameters OK if they have default values
Box(Box& other, int i = 42, string label = "Box");
Ao definir um construtor de cópia, você também deve definir um operador de atribuição de cópia (=). Para obter mais informações, consulte Construtores de atribuição e cópia e operadores de atribuição de cópia.
Você pode impedir que seu objeto seja copiado definindo o construtor copy como excluído:
Box (const Box& other) = delete;
A tentativa de copiar o objeto produz o erro C2280: tentar fazer referência a uma função excluída.
Construtores Move
Um construtor move é uma função de membro especial que move a propriedade dos dados de um objeto existente para uma nova variável sem copiar os dados originais. Ele usa uma referência rvalue como seu primeiro parâmetro, e quaisquer parâmetros posteriores devem ter valores padrão. Os construtores Move podem aumentar significativamente a eficiência do seu programa ao passar objetos grandes.
Box(Box&& other);
O compilador escolhe um construtor move quando o objeto é inicializado por outro objeto do mesmo tipo, se o outro objeto está prestes a ser destruído e não precisa mais de seus recursos. O exemplo a seguir mostra um caso em que um construtor move é selecionado pela resolução de sobrecarga. No construtor que chama get_Box(), o valor retornado é um xvalue (valor eXpiring). Não é atribuído a nenhuma variável e, portanto, está prestes a sair do escopo. Para fornecer motivação para este exemplo, vamos dar ao Box um grande vetor de strings que representam seu conteúdo. Em vez de copiar o vetor e suas cadeias de caracteres, o construtor move "rouba" do valor expirante "box" para que o vetor agora pertença ao novo objeto. A chamada para é tudo o std::move que é necessário porque ambas as vector classes implementam string seus próprios construtores de movimento.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Box {
public:
Box() { std::cout << "default" << std::endl; }
Box(int width, int height, int length)
: m_width(width), m_height(height), m_length(length)
{
std::cout << "int,int,int" << std::endl;
}
Box(Box& other)
: m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
{
std::cout << "copy" << std::endl;
}
Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
{
m_contents = std::move(other.m_contents);
std::cout << "move" << std::endl;
}
int Volume() { return m_width * m_height * m_length; }
void Add_Item(string item) { m_contents.push_back(item); }
void Print_Contents()
{
for (const auto& item : m_contents)
{
cout << item << " ";
}
}
private:
int m_width{ 0 };
int m_height{ 0 };
int m_length{ 0 };
vector<string> m_contents;
};
Box get_Box()
{
Box b(5, 10, 18); // "int,int,int"
b.Add_Item("Toupee");
b.Add_Item("Megaphone");
b.Add_Item("Suit");
return b;
}
int main()
{
Box b; // "default"
Box b1(b); // "copy"
Box b2(get_Box()); // "move"
cout << "b2 contents: ";
b2.Print_Contents(); // Prove that we have all the values
char ch;
cin >> ch; // keep window open
return 0;
}
Se uma classe não definir um construtor move, o compilador gerará um implícito se não houver nenhum construtor de cópia declarado pelo usuário, operador de atribuição de cópia, operador de atribuição de movimento ou destruidor. Se nenhum construtor move explícito ou implícito for definido, as operações que de outra forma usariam um construtor move usarão o construtor copy. Se uma classe declara um construtor move ou operador de atribuição move, o construtor copy implicitamente declarado é definido como excluído.
Um construtor move declarado implicitamente é definido como excluído se quaisquer membros que são tipos de classe não tiverem um destruidor ou se o compilador não puder determinar qual construtor usar para a operação move.
Para obter mais informações sobre como escrever um construtor de movimento não trivial, consulte Construtores de movimento e operadores de atribuição de movimento (C++).
Construtores explicitamente padronizados e excluídos
Você pode explicitamente padrão copiar construtores, construtores padrão, mover construtores, copiar operadores de atribuição, mover operadores de atribuição e destruidores. Você pode excluir explicitamente todas as funções especiais de membro.
class Box2
{
public:
Box2() = delete;
Box2(const Box2& other) = default;
Box2& operator=(const Box2& other) = default;
Box2(Box2&& other) = default;
Box2& operator=(Box2&& other) = default;
//...
};
Para obter mais informações, consulte Funções explicitamente padrão e excluídas.
Construtores Constexpr
Um construtor pode ser declarado como constexpr se:
- ou é declarado como inadimplente ou então satisfaz todas as condições para funções constexpr em geral;
- a turma não tem aulas base virtuais;
- cada um dos parâmetros é um tipo literal;
- o corpo não é um bloqueio de função;
- todos os membros de dados não estáticos e subobjetos de classe base são inicializados;
- se a classe for (a) um sindicato com membros variantes, ou (b) tiver sindicatos anônimos, apenas um dos membros do sindicato será inicializado;
- Cada membro de dados não estáticos do tipo de classe e todos os subobjetos de classe base têm um construtor Constexpr
Construtores da lista de inicializadores
Se um construtor usa um std::initializer_list<T> como seu parâmetro, e quaisquer outros parâmetros têm argumentos padrão, esse construtor é selecionado na resolução de sobrecarga quando a classe é instanciada através da inicialização direta. Você pode usar o initializer_list para inicializar qualquer membro que possa aceitá-lo. Por exemplo, suponha que a classe Box (mostrada anteriormente) tenha um std::vector<string> membro m_contents. Você pode fornecer um construtor como este:
Box(initializer_list<string> list, int w = 0, int h = 0, int l = 0)
: m_contents(list), m_width(w), m_height(h), m_length(l)
{}
E, em seguida, crie objetos Box como este:
Box b{ "apples", "oranges", "pears" }; // or ...
Box b2(initializer_list<string> { "bread", "cheese", "wine" }, 2, 4, 6);
Construtores explícitos
Se uma classe tem um construtor com um único parâmetro, ou se todos os parâmetros, exceto um, têm um valor padrão, o tipo de parâmetro pode ser implicitamente convertido para o tipo de classe. Por exemplo, se a Box classe tiver um construtor como este:
Box(int size): m_width(size), m_length(size), m_height(size){}
É possível inicializar uma caixa assim:
Box b = 42;
Ou passe um int para uma função que leva uma caixa:
class ShippingOrder
{
public:
ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}
private:
Box m_box;
double m_postage;
}
//elsewhere...
ShippingOrder so(42, 10.8);
Essas conversões podem ser úteis em alguns casos, mas na maioria das vezes podem levar a erros sutis, mas graves, em seu código. Como regra geral, você deve usar a explicit palavra-chave em um construtor (e operadores definidos pelo usuário) para evitar esse tipo de conversão de tipo implícita:
explicit Box(int size): m_width(size), m_length(size), m_height(size){}
Quando o construtor é explícito, esta linha causa um erro de compilador: ShippingOrder so(42, 10.8);. Para obter mais informações, consulte User-Defined Conversões de tipo.
Ordem de construção
Um construtor executa seu trabalho nesta ordem:
Ele chama construtores de classe base e membro na ordem de declaração.
Se a classe for derivada de classes base virtuais, ela inicializará os ponteiros 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ções virtuais da classe para permitir a ligação correta de chamadas de função virtual ao código.
Ele executa qualquer código em seu corpo de função.
O exemplo a seguir mostra a ordem na qual a classe base e os construtores membro são chamados no construtor para uma classe derivada. Primeiro, o construtor base é chamado. Em seguida, os membros da classe base são inicializados na ordem em que aparecem na declaração de classe. Finalmente, o construtor derivado é chamado.
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() { cout << "Contained1 ctor\n"; }
};
class Contained2 {
public:
Contained2() { cout << "Contained2 ctor\n"; }
};
class Contained3 {
public:
Contained3() { cout << "Contained3 ctor\n"; }
};
class BaseContainer {
public:
BaseContainer() { cout << "BaseContainer ctor\n"; }
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() { cout << "DerivedContainer ctor\n"; }
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
}
Aqui está o resultado:
Contained1 ctor
Contained2 ctor
BaseContainer ctor
Contained3 ctor
DerivedContainer ctor
Um construtor de classe derivado sempre chama um construtor de classe base, para que ele possa confiar em classes base completamente construídas antes que qualquer trabalho extra seja feito. Os construtores de classe base são chamados em ordem de derivação — por exemplo, se ClassA é derivado de ClassB, que é derivado de ClassC, o ClassC construtor é chamado primeiro, depois o construtor, então o ClassBClassA construtor.
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);
}
Se um construtor lança uma exceção, a ordem de destruição é o inverso da ordem de construção:
O código no corpo da função do construtor é desenrolado.
Os objetos de classe base e membro são destruídos, na ordem inversa da declaração.
Se o construtor não estiver delegando, todos os objetos e membros de classe base totalmente construídos serão destruídos. No entanto, como o objeto em si não é totalmente construído, o destruidor não é executado.
Construtores derivados e inicialização de agregados estendida
Se o construtor de uma classe base não for público, mas acessível a uma classe derivada, você não poderá usar chaves vazias para inicializar um objeto do tipo derivado no /std:c++17 modo e posteriormente no Visual Studio 2017 e posterior.
O exemplo a seguir mostra o comportamento compatível com C++14:
struct Derived;
struct Base {
friend struct Derived;
private:
Base() {}
};
struct Derived : Base {};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
// which can call Base ctor.
Em C++17, Derived agora é considerado um tipo agregado. Isso significa que a inicialização de Base via o construtor padrão privado acontece diretamente, como parte da regra de inicialização de agregado estendida. Anteriormente, o Base construtor privado era chamado através do Derived construtor, e conseguiu por causa da friend declaração.
O exemplo a seguir mostra o comportamento do C++17 no Visual Studio 2017 e posterior no /std:c++17 modo:
struct Derived;
struct Base {
friend struct Derived;
private:
Base() {}
};
struct Derived : Base {
Derived() {} // add user-defined constructor
// to call with {} initialization
};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // error C2248: 'Base::Base': can't access
// private member declared in class 'Base'
Construtores para classes que têm herança múltipla
Se uma classe for derivada de várias classes base, os construtores de classe base serão invocados na ordem em que estão listados na declaração da classe derivada:
#include <iostream>
using namespace std;
class BaseClass1 {
public:
BaseClass1() { cout << "BaseClass1 ctor\n"; }
};
class BaseClass2 {
public:
BaseClass2() { cout << "BaseClass2 ctor\n"; }
};
class BaseClass3 {
public:
BaseClass3() { cout << "BaseClass3 ctor\n"; }
};
class DerivedClass : public BaseClass1,
public BaseClass2,
public BaseClass3
{
public:
DerivedClass() { cout << "DerivedClass ctor\n"; }
};
int main() {
DerivedClass dc;
}
Você deve esperar a seguinte saída:
BaseClass1 ctor
BaseClass2 ctor
BaseClass3 ctor
DerivedClass ctor
Delegação de construtores
Um construtor delegador chama um construtor diferente na mesma classe para fazer parte do trabalho de inicialização. Esse recurso é útil quando você tem vários construtores que todos têm que executar um trabalho semelhante. Você pode escrever a lógica principal em um construtor e invocá-la de outros. No exemplo trivial a seguir, Box(int) delega seu trabalho a Box(int,int,int):
class Box {
public:
// Default constructor
Box() {}
// Initialize a Box with equal dimensions (i.e. a cube)
Box(int i) : Box(i, i, i) // delegating constructor
{}
// Initialize a Box with custom dimensions
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height)
{}
//... rest of class as before
};
O objeto criado pelos construtores é totalmente inicializado assim que qualquer construtor é concluído. Para obter mais informações, consulte Delegando construtores.
Herdando construtores (C++11)
Uma classe derivada pode herdar os construtores de uma classe base direta usando uma using declaração, conforme mostrado no exemplo a seguir:
#include <iostream>
using namespace std;
class Base
{
public:
Base() { cout << "Base()" << endl; }
Base(const Base& other) { cout << "Base(Base&)" << endl; }
explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }
private:
int num;
char letter;
};
class Derived : Base
{
public:
// Inherit all constructors from Base
using Base::Base;
private:
// Can't initialize newMember from Base constructors.
int newMember{ 0 };
};
int main()
{
cout << "Derived d1(5) calls: ";
Derived d1(5);
cout << "Derived d1('c') calls: ";
Derived d2('c');
cout << "Derived d3 = d2 calls: " ;
Derived d3 = d2;
cout << "Derived d4 calls: ";
Derived d4;
}
/* Output:
Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()*/
Visual Studio 2017 e posterior: A using instrução no /std:c++17 modo e posterior traz para o escopo todos os construtores da classe base, exceto aqueles que têm uma assinatura idêntica aos construtores na classe derivada. Em geral, é melhor usar herdando construtores quando a classe derivada declara que não há novos membros de dados ou construtores.
Um modelo de classe pode herdar todos os construtores de um argumento type se esse tipo especificar uma classe base:
template< typename T >
class Derived : T {
using T::T; // declare the constructors from T
// ...
};
Uma classe derivada não pode herdar de várias classes base se essas classes base tiverem construtores que tenham uma assinatura idêntica.
Construtores e classes compostas
As classes que contêm membros de tipo de classe são conhecidas como classes compostas. Quando um membro de tipo de classe de uma classe composta é criado, o construtor é chamado antes do próprio construtor da classe. Quando uma classe contida não tem um construtor padrão, você deve usar uma lista de inicialização no construtor da classe composta. No exemplo anteriorStorageBox, se você alterar o tipo da variável membro para uma nova m_label classe, deverá chamar o construtor da Label classe base e inicializar a m_labelStorageBox variável no construtor:
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"});
}
Nesta secção
- Construtores de cópia e operadores de atribuição de cópia
- Mover construtores e mover operadores de atribuição
- Delegando construtores