Compartilhar via


Construtores (C++)

Para personalizar como uma classe inicializa seus membros ou para invocar funções quando um objeto da classe é criado, defina um construtor. Um construtor tem o mesmo nome que a classe e nenhum valor retornado. É possível 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 da 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 membro. É uma maneira mais eficiente de inicializar membros de 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 init de membro:

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)
}
  • Construtores podem ser declarados como inline, explicit, friend ou constexpr.
  • Um construtor pode inicializar um objeto que foi declarado como const, volatile ou const volatile. O objeto se torna const após a conclusão do construtor.
  • Para definir um construtor em um arquivo de implementação, dê a ele um nome qualificado como qualquer outra função membro: Box::Box(){...}.

Listas de inicializadores de membro

Opcionalmente, um construtor pode ter uma lista de inicializadores de membro, que inicializa os membros de classe antes da execução do corpo do construtor. (Uma lista de inicializadores de membro não é a mesma coisa que uma lista de inicializadores do tipo std::initializer_list<T>.)

Prefira listas de inicializadores de membro em vez de atribuir valores no corpo do construtor. Uma lista de inicializadores de membro inicializa diretamente os membros. O exemplo a seguir mostra a lista de inicializadores de membro, que consiste em todas as expressões identifier(argument) após os dois-pontos:

    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

O identificador deve se referir a um membro de classe; ele é 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>.

Membros const e membros do tipo de referência devem ser inicializados na lista de inicializadores de membro.

Para garantir que as classes base sejam totalmente inicializadas antes da execução do construtor derivado, chame quaisquer construtores de classe base parametrizados na lista de inicializadores.

Construtores padrão

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 especiais. Se nenhum construtor for declarado em uma classe, o compilador fornecerá um construtor padrão inline implícito.

#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ê depender de um construtor padrão implícito, certifique-se de inicializar os membros na definição de classe, conforme 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 forma, 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 por compilador será definido como excluído se os membros da classe não forem construíveis 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 acessíveis. Todos os membros de dados do tipo de referência e todos os membros const devem ter um inicializador de membro padrão.

Quando você chama um construtor padrão gerado por 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?)
}

Essa instruçã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 sobre outras coisas, a expressão é tratada como uma declaração de função. Para obter mais informações, consulte Most Vexing Parse.

Se qualquer 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 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 de colchete. Por exemplo, dado o bloco de códigos anterior, uma matriz de Boxes não pode ser declarada assim:

Box boxes[3]; // C2512: no appropriate default constructor available

No entanto, é possível 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 de cópia inicializa um objeto copiando os valores do membro de um objeto do mesmo tipo. Se os membros da classe forem todos tipos simples, como valores escalares, o construtor de cópia gerado pelo compilador será suficiente e você não precisará definir o seu próprio. Se a classe exigir uma inicialização mais complexa, você precisará implementar um construtor de cópia personalizado. Por exemplo, se um membro de classe for um ponteiro, será necessário definir um construtor de cópia para alocar memória nova e copiar os valores do objeto apontado para o outro. O construtor de cópia gerado pelo compilador simplesmente copia o ponteiro para 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");

Quando você define um construtor de cópia, 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 o objeto seja copiado definindo o construtor de cópia como excluído:

    Box (const Box& other) = delete;

Tentar copiar o objeto produz o erro C2280: tentativa de referenciar a uma função excluída.

Construtores de movimento

Um construtor de movimento é uma função 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 todos os parâmetros posteriores devem ter valores padrão. Construtores de movimento podem aumentar significativamente a eficiência do programa passando objetos grandes.

Box(Box&& other);

O compilador escolhe um construtor de movimento quando o objeto é inicializado por outro objeto do mesmo tipo, se o outro objeto estiver prestes a ser destruído e não precisar mais de seus recursos. O exemplo a seguir mostra um caso quando um construtor de movimento é selecionado pela resolução de sobrecarga. No construtor que chama get_Box(), o valor retornado é um xvalue (valor eXpiring). Ele não é atribuído a nenhuma variável e, portanto, está quase saindo do escopo. Para fundamentar esse exemplo, vamos dar ao Box um grande vetor de cadeias de caracteres que representam seu conteúdo. Em vez de copiar o vetor e suas cadeias de caracteres, o construtor de movimentação o "rouba" do valor expirado "box" para que o vetor agora pertença ao novo objeto. A chamada std::move é tudo o que é necessário porque ambas as classes vector e string implementam 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 de movimento, o compilador gerará um implícito se não houver 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 de movimento explícito ou implícito for definido, as operações que, de outra forma usariam um construtor de movimentação, usarão o construtor de cópia. Se uma classe declara um construtor de movimento ou um operador de atribuição de movimento, o construtor de cópia declarado implicitamente é definido como excluído.

Um construtor de movimento declarado implicitamente é definido como excluído se os 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 de movimentação.

Para obter mais informações sobre como gravar um construtor de movimentação não trivial, consulte Construtores de Movimento e Operadores de Atribuição de Movimento (C++).

Construtores explicitamente usados como padrão e excluídos

É possível usar explicitamente construtores de cópia padrão, construtores padrão, construtores de movimento, operadores de atribuição de cópia, operadores de atribuição de movimento e destruidores e destruidores. É possível excluir explicitamente todas as funções de membro especiais.

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 usadas como padrão e excluídas.

constexpr constructors

Um construtor pode ser declarado como constexpr se

  • ele for declarado como padrão ou então atender a todas as condições para funções constexpr em geral;
  • a classe não tiver classes base virtuais;
  • cada um dos parâmetros for um tipo literal;
  • o corpo não for um bloco try de função;
  • todos os membros de dados não estáticos e subobjetos de classe base forem inicializados;
  • se a classe for (a) uma união com membros variantes ou (b) tiver uniões anônimas, somente um dos membros da união será inicializado;
  • todos os membros de dados não estáticos do tipo de classe e todos os subobjetos de classe base tiverem um construtor constexpr

Construtores de lista de inicializadores

Se um construtor usar um std::initializer_list<T> como seu parâmetro e qualquer outro parâmetro tiver argumentos padrão, esse construtor será selecionado em resolução de sobrecarga quando a classe for instanciada por meio de inicialização direta. É possível usar o initializer_list para inicializar qualquer membro que possa aceitá-lo. Por exemplo, presuma que a classe Box (mostrada anteriormente) tenha um membro std::vector<string>m_contents. Você poderá 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, criar 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 tiver um construtor com um único parâmetro ou se todos os parâmetros exceto um tiverem um valor padrão, o tipo de parâmetro poderá ser convertido implicitamente no tipo de classe. Por exemplo, se a classe Box tiver um construtor como este:

Box(int size): m_width(size), m_length(size), m_height(size){}

Será possível inicializar um Box como este:

Box b = 42;

Ou passar um int para uma função que usa um Box:

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, embora mais frequentemente possam levar a erros sutis, mas graves, no código. Como regra geral, você deve usar a palavra-chave explicit em um construtor (e operadores definidos pelo usuário) para impedir esse tipo de conversão de tipo implícito:

explicit Box(int size): m_width(size), m_length(size), m_height(size){}

Quando o construtor é explícito, essa linha causa um erro do compilador: ShippingOrder so(42, 10.8);. Para saber mais, confira Conversões de tipo definido pelo usuário.

Ordem de construção

Um construtor executa seu trabalho nesta ordem:

  1. Chama a classe base e os construtores membros na ordem da declaração.

  2. Se a classe for derivada de classes base virtuais, ela inicializará os ponteiros de base virtuais do objeto.

  3. 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.

  4. 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 base é chamado. Em seguia, os membros de classe base não virtuais são inicializados na ordem em que aparecem na declaração de classe. Por fim, 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;
}

Esta é a saída:

Contained1 ctor
Contained2 ctor
BaseContainer ctor
Contained3 ctor
DerivedContainer ctor

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 último, 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);
}

Se um construtor gerar uma exceção, a ordem de destruição será a inversa da ordem de construção:

  1. O código no corpo da função de construtor é liberado.

  2. Os objetos de classe base e de membros são destruídos, na ordem inversa da declaração.

  3. Se o construtor for não delegado, 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 derivados e inicialização de agregação estendida

Se o construtor de uma classe base for não público, mas acessível a uma classe derivada, não será possível usar chaves vazias para inicializar um objeto do tipo derivado no modo /std:c++17 e posterior no Visual Studio 2017 e mais recente.

O exemplo a seguir mostra o comportamento de conformidade do 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.

No C++ 17, Derived agora é considerado um tipo de agregação. Isso significa que a inicialização de Base por meio do construtor padrão privado acontece diretamente como parte da regra de inicialização de agregação estendida. Anteriormente, o construtor privado Base era chamado por meio do construtor Derived, e isso era bem-sucedido devido à declaração friend.

O exemplo a seguir mostra o comportamento do C++17 no Visual Studio 2017, no modo /std:c++17:

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 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 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;
}

O seguinte resultado é esperado:

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 em que todos precisam executar trabalhos semelhantes. Você pode gravar a lógica principal em um construtor e invocá-la de outros construtores. No exemplo trivial a seguir, Box(int) delega seu trabalho para 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 é inicializado totalmente assim que o construtor é concluído. Para obter mais informações, confira Construtores Delegadores.

Construtores herdados (C++11)

Uma classe derivada pode herdar os construtores de uma classe base direta usando uma declaração using, 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 instrução using no modo /std:c++17 e posterior coloca no escopo todos os construtores da classe base, exceto os que tenham uma assinatura idêntica aos construtores na classe derivada. Em geral, é melhor usar construtores herdados quando a classe derivada declara nenhum novo membro de dados ou construtor.

Um modelo de classe pode herdar todos os construtores de um argumento de tipo 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 poderá 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 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"});
}

Nesta seção

Confira também

Classes e structs