Каковы все функции-члены, созданные компилятором для класса? Это случается все время?

Каковы все функции-члены, созданные компилятором для класса? Это случается все время? как деструктор. Меня интересует, создан ли он для всех классов, и зачем нужен конструктор по умолчанию?

5 ответов

Решение

C++98/03

Если они нужны,

  1. компилятор сгенерирует для вас конструктор по умолчанию, если вы не объявите свой собственный конструктор.
  2. компилятор сгенерирует для вас конструктор копирования, если вы не объявите свой собственный.
  3. компилятор сгенерирует для вас оператор присваивания копии, если вы не объявите свой собственный.
  4. компилятор создаст для вас деструктор, если вы не объявите свой собственный.

Как сказал Петер в полезном комментарии, все они генерируются компилятором только тогда, когда они необходимы. (Разница в том, что когда компилятор не может их создать, это нормально, если они не используются.)


C++ 11

C++ 11 добавляет следующие правила, которые также справедливы для C++ 14 (кредиты для towi, см. Этот комментарий):

  • Компилятор генерирует конструктор перемещения, если
    • не существует объявленного пользователем конструктора копирования, и
    • не существует объявленного пользователем оператора копирования, и
    • нет оператора, назначенного пользователем, и
    • нет деструктора, объявленного пользователем,
    • он не помечен как удаленный,
    • и все члены и базы являются подвижными.
  • Аналогично для оператора назначения перемещения: генерируется, если пользователь не определен
    • не существует объявленного пользователем конструктора копирования, и
    • не существует объявленного пользователем оператора копирования, и
    • нет объявленного пользователем конструктора перемещения и
    • нет деструктора, объявленного пользователем,
    • он не помечен как удаленный,
    • и все члены и базы являются подвижными.

Обратите внимание, что эти правила немного сложнее правил C++03 и имеют больше смысла на практике.

Для более легкого понимания того, что из вышеперечисленного здесь, пункты для Thing:

class Thing {
public:
    Thing();                        // default constructor
    Thing(const Thing&);            // copy c'tor
    Thing& operator=(const Thing&); // copy-assign
    ~Thing();                       // d'tor
    // C++11:
    Thing(Thing&&);                 // move c'tor
    Thing& operator=(Thing&&);      // move-assign
};

И в качестве дальнейшего прочтения, если вы новичок в C++, рассмотрите проект, который не требует от вас реализации какой-либо из последних пяти, то есть "Правило нуля" ( R. Martinho Fernandes).

Вы имеете в виду "определено" как "создано"?

$ 12.1 - "Конструктор по умолчанию (12.1), конструктор копирования и оператор назначения копирования (12.8) и деструктор (12.4) являются специальными функциями-членами.

Если "создано" означает "определено", то вот важные части из стандарта C++.

- Неявно объявленный конструктор по умолчанию для класса неявно определяется, когда он используется для создания объекта своего типа (1.8).

-Если в классе нет объявленного пользователем деструктора, деструктор объявляется неявно. Неявно объявленный деструктор неявно определяется, когда он используется для уничтожения объекта его класса.

-Если определение класса явно не объявляет конструктор копирования, он объявляется неявно. Неявно объявленный конструктор копирования неявно определяется, если он используется для инициализации объекта его типа класса из копии объекта его типа класса или типа класса, производного от его типа класса).

-Если определение класса явно не объявляет оператор присваивания копии, он объявляется неявно. Неявно объявленный оператор присваивания копии неявно определяется, когда объекту его типа класса присваивается значение его типа класса или значение типа класса, производное от его типа класса.

C++17 N4659 стандартная версия

https://github.com/cplusplus/draft/blob/master/papers/n4659.pdf 6.1 "Объявления и определения" содержит примечание, которое, вероятно, суммирует их все:

3 [Примечание: В некоторых случаях реализации C++ неявно определяют конструктор по умолчанию (15.1), конструктор копирования (15.8), конструктор перемещения (15.8), оператор назначения копирования (15.8), оператор назначения перемещения (15.8) или деструктор (15.4) функции-члены. - конец примечания] [Пример: дано

#include <string>

struct C {
  std::string s;         // std::string is the standard library class (Clause 24)
};

int main() {
  C a;
  C b = a;
  b = a;
}

реализация будет неявно определять функции, чтобы сделать определение C эквивалентным

struct C {
  std::string s;
  C() : s() { }
  C(const C& x): s(x.s) { }
  C(C&& x): s(static_cast<std::string&&>(x.s)) { }
  // : s(std::move(x.s)) { }
  C& operator=(const C& x) { s = x.s; return *this; }
  C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
  // { s = std::move(x.s); return *this; }
  ~ C() { }
};

- конец примера]

Условия, при которых они объявляются, поясняются в: Условия автоматической генерации ctor по умолчанию / copy / move ctor и оператора копирования / move назначений?

Отличный способ убедиться, что у чего-то есть значение по умолчанию, это попытаться использовать его = default как объяснено в: Что означает "по умолчанию" после объявления функции класса?

Пример ниже делает это, а также выполняет все неявно определенные функции.

#include <cassert>
#include <string>

struct Default {
    int i;
    Default()                          = default;
    Default(const Default&)            = default;
    Default& operator=(Default&)       = default;
    Default& operator=(const Default&) = default;
    Default(Default&&)                 = default;
    Default& operator=(Default&&)      = default;
    ~Default()                         = default;
};

struct Instrument {
    int i;
    static std::string last_call;
    Instrument()                             { last_call = "ctor"; }
    Instrument(const Instrument&)            { last_call = "copy ctor"; }
    Instrument& operator=(Instrument&)       { last_call = "copy assign"; return *this; }
    Instrument& operator=(const Instrument&) { last_call = "copy assign const"; return *this; }
    Instrument(Instrument&&)                 { last_call = "move ctor";  }
    Instrument& operator=(Instrument&&)      { last_call = "move assign"; return *this; }
    ~Instrument()                            { last_call = "dtor"; }
};
std::string Instrument::last_call;

int main() {
    // See what the default constructors are doing.
    {
        // Default constructor.
        Default ctor;
        // i is uninitialized.
        // std::cout << ctor.i << std::endl;
        ctor.i = 1;

        // Copy constructor.
        Default copy_ctor(ctor);
        assert(copy_ctor.i = 1);

        // Copy assignment.
        Default copy_assign;
        copy_assign = ctor;
        assert(copy_assign.i = 1);

        // Copy assignment const.
        const Default const_ctor(ctor);
        Default copy_assign_const;
        copy_assign_const = const_ctor;
        assert(copy_assign_const.i == 1);

        // Move constructor.
        Default move_ctor(std::move(ctor));
        assert(move_ctor.i == 1);

        // Move assignment.
        Default move_assign;
        move_assign = std::move(ctor);
        assert(move_assign.i == 1);
    }

    // Check that the constructors are called by these calls.
    {
        // Default constructor.
        Instrument ctor;
        assert(Instrument::last_call == "ctor");

        // Copy constructor.
        Instrument copy_ctor(ctor);
        assert(Instrument::last_call == "copy ctor");

        // Copy assignment.
        copy_ctor = ctor;
        assert(Instrument::last_call == "copy assign");

        // Copy assignment const.
        const Instrument const_ctor(ctor);
        Instrument copy_assign_const;
        copy_assign_const = const_ctor;
        assert(Instrument::last_call == "copy assign const");

        // Move constructor.
        Instrument move_ctor(std::move(ctor));
        assert(Instrument::last_call == "move ctor");

        // Move assignment.
        Instrument move_assign;
        move_assign = std::move(ctor);
        assert(Instrument::last_call == "move assign");

        // Destructor.
        {
            Instrument dtor;
        }
        assert(Instrument::last_call == "dtor");
    }
}

GitHub вверх по течению.

Протестировано с GCC 7.3.0:

g++ -std=c++11 implicitly_defined.cpp

По умолчанию, если он не реализован пользователем, компилятор добавляет некоторые функции-члены в класс. Те, которые называются большой четверкой:

  • конструктор по умолчанию
  • конструктор копирования
  • оператор копирования (присваивание)
  • деструктор

В зависимости от типов членов и перечисленных вами функций-членов, вы сами создадите их не все.

Другие ответы рассказали вам, что создано, и что компилятор может генерировать их только при использовании.

Меня беспокоит, создан ли он для всех классов...

Почему это касается? Думаете, это создает нежелательный код в исполняемом файле? Маловероятно, но вы можете проверить достаточно легко с вашей средой.

Или, возможно, вы беспокоились о том, что он не может создать конструктор, если вы хотите его? Не о чем беспокоиться... они всегда создаются при необходимости и не предоставляются пользователем.

... а зачем нужен конструктор по умолчанию?

Потому что у классов могут быть объекты внутри них со своими собственными деструкторами, которые нужно систематически вызывать. Например, учитывая...

struct X
{
    std::string a;
    std::string b;
};

... деструктор по умолчанию обеспечивает работу деструкторов a и b.

Другие вопросы по тегам