Какие методы по умолчанию и как их использовать?

Возможный дубликат:
В чем смысл дефолтных функций в С++ 11?

С++ 11 представил дефолтные методы (например, void myMethod() = default;).

Что он делает с методами (как ведут себя методы после default ed). Как правильно их использовать (каковы его применения)?

Ответ 1

Существует ряд членов класса, которые считаются "специальными функциями-членами" по стандарту С++. Это:

  • Конструктор по умолчанию (конструктор, который можно вызвать без параметров).
  • Конструктор копирования (конструктор, который может быть вызван с одним параметром, который является типом объекта в качестве ссылки lvalue).
  • Оператор присваивания копии (оператор = перегрузка, который может быть вызван с одним параметром, который является типом объекта, либо ссылкой на значение lvalue, либо значением).
  • Конструктор перемещения (конструктор, который может быть вызван с одним параметром, который является типом объекта в качестве ссылки rvalue).
  • Оператор присваивания переадресации (оператор = перегрузка, который может быть вызван с одним параметром, который является типом объекта, либо как ссылка rvalue, либо значение).
  • Деструктор.

Эти функции-члены являются особыми в том, что язык делает с ними специальные вещи по типам. Еще одна особенность, которая делает их особенными, заключается в том, что компилятор может предоставить определения для них, если вы этого не сделаете. Это единственные функции, с помощью которых можно использовать синтаксис = default;.

Проблема заключается в том, что компилятор будет предоставлять определение только при определенных условиях. То есть существуют условия, при которых определение не будет предоставлено.

Я не буду перебирать весь список, но один пример - это то, о чем говорили другие. Если вы создадите конструктор для типа, который не является специальным конструктором (т.е. Не является одним из конструкторов, упомянутых выше), конструктор по умолчанию не будет автоматически сгенерирован. Поэтому этот тип:

struct NoDefault
{
  NoDefault(float f);
};

NoDefault не может быть сконфигурирован по умолчанию. Поэтому он не может использоваться в любом контексте, где требуется построение по умолчанию. Вы не можете сделать NoDefault() для создания временного. Вы не можете создавать массивы NoDefault (так как они построены по умолчанию). Вы не можете создать std::vector<NoDefault> и вызвать конструктор размеров без предоставления значения для копирования из или любой другой операции, требующей, чтобы тип был DefaultConstructible.

Однако вы можете сделать это:

struct UserDefault
{
  UserDefault() {}
  UserDefault(float f);
};

Это все исправит, правда?

НЕПРАВИЛЬНО!

Это не одно и то же:

struct StdDefault
{
  StdDefault() = default;
  StdDefault(float f);
};

Почему? Потому что StdDefault - тривиальный тип. Что это значит? Я не буду объяснять все это, но перейдите сюда для подробностей. Создание типов тривиальных часто является полезной функцией, когда вы можете это сделать.

Одно из требований тривиального типа заключается в том, что он не имеет предоставленного пользователем конструктора по умолчанию. UserDefault имеет предоставленный, хотя он делает то же самое, что и созданный компилятором. Поэтому UserDefault не является тривиальным. StdDefault является тривиальным типом, потому что синтаксис = default означает, что компилятор его сгенерирует. Поэтому он не предоставляется пользователям.

Синтаксис = default сообщает компилятору: "Генерируйте эту функцию в любом случае, даже если вы обычно этого не сделали". Это важно, чтобы гарантировать, что класс является тривиальным типом, поскольку вы не можете реально реализовать специальные члены так, как это может сделать компилятор. Это позволяет заставить компилятор генерировать функцию, даже если она не будет.

Это очень полезно во многих случаях. Например:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

private:
  std::unique_ptr<SomeType> m_ptr;
};

Мы должны написать каждую из этих функций, чтобы класс копировал содержимое unique_ptr; нет способа избежать этого. Но мы также хотим, чтобы это было подвижным, и операторы конструктора/назначения перемещения не будут автоматически генерироваться для нас. Было бы глупо повторять их (и подвергать ошибкам, если мы добавим больше членов), поэтому мы можем использовать синтаксис = default:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing(UniqueThing &&ptr) = default;
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

  UniqueThing &operator =(UniqueThing &&ptr) = default;

private:
  std::unique_ptr<SomeType> m_ptr;
};

Ответ 2

Фактически, default может применяться только к специальным методам - ​​т.е. конструкторам, деструкторам, оператору присваивания:

8.4.2 Явно-дефолтные функции [dcl.fct.def.default]

1 Определение функции вида: attribute-specifier-seqopt decl-specifier-seqopt declarator = default ;называется явно дефолтным определением. Функция, которая явно дефолтна, должна
- быть специальной функцией-членом,
- имеют один и тот же заявленный тип функции (за исключением, возможно, разных реф-квалификаторов и за исключением того, что в случай конструктора копирования или оператора присваивания копии, тип параметра может быть "ссылкой на non-const T", где T - имя класса функций-членов), как если бы оно было объявлено неявно, и
- не имеют аргументов по умолчанию.

Они ведут себя так же, как если бы компилятор сгенерировал их и полезны для таких случаев, как:

class Foo{
     Foo(int) {}
};

здесь конструктор по умолчанию не существует, поэтому Foo f; недействителен. Однако вы можете сообщить компилятору, что вы хотите использовать конструктор по умолчанию без необходимости его реализации самостоятельно:

class Foo{
     Foo() = default;
     Foo(int) {}
};

РЕДАКТИРОВАТЬ: В комментариях @Nicol Bolas в комментариях это действительно не объясняет, почему вы предпочитаете это более Foo() {}. Я предполагаю, что если у вас есть класс w/реализаций, разделенных в файле реализации, то определение вашего класса (если вы не используете =default) будет выглядеть так:

class Foo{
     Foo();
     Foo(int);
};

Я предполагаю, что =default предназначен для предоставления идиоматического способа сказать вам: "Мы ничего не делаем в конструкторе". С приведенным выше определением класса конструктор по умолчанию может очень хорошо не инициализировать элементы класса. С =default у вас есть эта гарантия, просто посмотрев на заголовок.

Ответ 3

Использование = default для конструктора или конструктора-копии означает, что компилятор будет генерировать эту функцию для вас во время компиляции. Это происходит обычно, когда вы исключаете соответствующие функции из class/struct. И в случае, когда вы определяете свой собственный конструктор, этот новый синтаксис позволяет явно указывать компилятору по умолчанию - построить конструктор, где он обычно не будет. Возьмем, к примеру:

struct S {

};

int main() {

    S s1; // 1
    S s2(s1); // 2

}

Компилятор сгенерирует как конструктор-копию, так и конструктор по умолчанию, что позволит нам создать экземпляр объекта S, как мы сделали (1), и скопировать s2 в s1 (2). Но если мы определяем наш собственный конструктор, мы обнаруживаем, что мы не можем создать экземпляр таким же образом:

struct S {
    S(int);
};

int main() {

    S s;    // illegal
    S s(5); // legal

}

Это не удается, потому что S получил заданный конструктор, а компилятор не будет генерировать значение по умолчанию. Но если мы все еще хотим, чтобы компилятор дал нам один, мы можем явно передать это с помощью default:

struct S {
    S(int);
    S() = default;
    S(const S &) = default;
};

int main() {

    S s;     // legal
    S s(5);  // legal

    S s2(s); // legal

}

Обратите внимание, однако, что только некоторые функции с определенными сигнатурами функций могут быть перегружены. Из этого следует, что это не будет выполнено, поскольку оно не является ни конструктором-копий, ни конструктором по умолчанию, ни оператором перемещения или присваивания:

struct S {
    S(int) = default; // error
};

Мы также можем явно определить оператор присваивания (как и конструктор default/copy-construct, по умолчанию он создается для нас, когда мы не выписываем наши собственные). Так возьмите, например:

struct S {
    int operator=(int) {
        // do our own stuff
    }
};

int main() {

    S s;

    s = 4;

}

Это скомпилируется, но в общем случае мы не будем работать в общем случае, когда мы хотим назначить другой объект S в S. Это связано с тем, что, поскольку мы написали свой собственный, компилятор не предоставляет его для нас. Здесь default входит в игру:

struct S {
    int operator=(int) {
        // do our own stuff
    }

    S & operator=(const S &) = default;
};

int main() {

    S s1, s2;

    s1 = s2; // legal

}

Но технически излишне определять оператор присваивания, копировать или перемещать конструктор по умолчанию, когда нет другой функции, "переопределяя" ее, поскольку компилятор в любом случае предоставит ее вам. Единственный случай, когда он не предоставит вам, - это то, где вы определяете свой собственный (или его вариант). Но в качестве предпочтения просить компилятор предоставить функцию по умолчанию, используя новый вышеупомянутый синтаксис, более ясен и проще видеть и понимать, если намеренно не учитывать компилятор.