Я не розумію, навіщо мені це робити:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Чому б просто не сказати:
S() {} // instead of S() = default;
навіщо вводити для цього новий синтаксис?
Я не розумію, навіщо мені це робити:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Чому б просто не сказати:
S() {} // instead of S() = default;
навіщо вводити для цього новий синтаксис?
Відповіді:
Конструктор за замовчуванням за замовчуванням конкретно визначається як той самий, що визначений користувачем конструктор за замовчуванням без списку ініціалізації та порожнього оператора.
§12.1 / 6 [class.ctor] Конструктор за замовчуванням, який за замовчуванням і не визначений як видалений, неявно визначається, коли він odr-використовується для створення об'єкта типу свого класу або коли він явно за замовчуванням після першого оголошення. Неявно визначений конструктор за замовчуванням виконує набір ініціалізацій класу, який був би виконаний написаним користувачем конструктором за замовчуванням для цього класу без ctor-ініціалізатора (12.6.2) та порожнього оператора-з'єднання. [...]
Однак, хоча обидва конструктори будуть вести себе однаково, надання порожньої реалізації впливає на деякі властивості класу. Даючи призначений користувачем конструктор, навіть якщо він нічого не робить, робить тип не сукупним, а також не тривіальним . Якщо ви хочете, щоб ваш клас був сукупним або тривіальним типом (або за транзитивністю, типом POD), тоді вам потрібно скористатися = default
.
§8.5.1 / 1 [dcl.init.aggr] Сукупність - це масив або клас, що не надає користувачеві конструктори, [і ...]
§12.1 / 5 [class.ctor] Конструктор за замовчуванням є тривіальним, якщо він не надається користувачем і [...]
§9 / 6 [клас] Тривіальний клас - це клас, який має тривіальний конструктор за замовчуванням та [...]
Демонструвати:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() { };
};
int main() {
static_assert(std::is_trivial<X>::value, "X should be trivial");
static_assert(std::is_pod<X>::value, "X should be POD");
static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}
Додатково, конструктор явно дефолтом зробить це constexpr
якби був неявний конструктор, а також дасть йому таку саму специфікацію винятків, яку мав би неявний конструктор. У випадку, який ви надали, неявний конструктор не був би constexpr
(тому що він залишив би дані про дані неініціалізованими), а також матиме порожню специфікацію винятків, тому різниці немає. Але так, у загальному випадку ви можете вручну вказати constexpr
та специфікацію винятку, щоб відповідати неявному конструктору.
Використання = default
приносить певну рівномірність, оскільки його можна також використовувати з конструкторами копіювання / переміщення та деструкторами. Наприклад, порожній конструктор копій не буде робити те ж саме, що конструктор копій за умовчанням (який виконує копію своїх членів). Використання = default
(або = delete
) синтаксису рівномірно для кожної з цих спеціальних функцій-членів робить ваш код легшим для читання, чітко вказуючи свій намір.
constexpr
конструктора (7.1.5), неявно визначений конструктор за замовчуванням є constexpr
."
constexpr
якби неявна заява була б, (б) неявно вважається такою ж специфікація винятків, як ніби вона була неявно оголошена (15.4), ... "У цьому конкретному випадку це не має ніякого значення, але загалом foo() = default;
має невелику перевагу перед foo() {}
.
constexpr
(оскільки член даних залишається неініціалізованим), а його специфікація винятків допускає всі винятки. Я зроблю це зрозуміліше.
constexpr
(з тим, що ви згадали, тут не варто змінювати): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};
Лише s1
помилка не відповідає s2
. І в лясканні, і в g ++.
У мене є приклад, який покаже різницю:
#include <iostream>
using namespace std;
class A
{
public:
int x;
A(){}
};
class B
{
public:
int x;
B()=default;
};
int main()
{
int x = 5;
new(&x)A(); // Call for empty constructor, which does nothing
cout << x << endl;
new(&x)B; // Call for default constructor
cout << x << endl;
new(&x)B(); // Call for default constructor + Value initialization
cout << x << endl;
return 0;
}
Вихід:
5
5
0
Як ми бачимо, виклик порожнього A () конструктора не ініціалізує членів, тоді як B () робить це.
n2210 наводить деякі причини:
Управління за замовчуванням має кілька проблем:
- Визначення конструктора сполучені; оголошення будь-якого конструктора придушує конструктор за замовчуванням.
- За замовчуванням деструктора не підходить для поліморфних класів, що вимагає чіткого визначення.
- Після того, як дефолт придушений, немає коштів відновити його.
- Реалізації за замовчуванням часто ефективніші, ніж реалізовані вручну.
- Реалізації за замовчуванням нетривіальні, що впливає на семантику типу, наприклад, робить тип не-POD.
- Немає засобів заборонити функцію спеціального члена або глобального оператора без оголошення замінника (нетривіальної).
type::type() = default; type::type() { x = 3; }
У деяких випадках тіло класу може змінюватися, не вимагаючи зміни визначення функції члена, оскільки за замовчуванням змінюється декларація додаткових членів.
Див. Правило трьох стає Правилом п’яти з C ++ 11? :
Зауважте, що конструктор переміщення та оператор присвоєння переміщення не будуть генеровані для класу, який явно декларує будь-яку з інших спеціальних функцій-членів, що конструктор копіювання та оператор призначення примірника копій не будуть генеровані для класу, який явно оголошує конструктор переміщення або переміщення оператор присвоєння, і що клас із явно оголошеним деструктором та неявно визначеним конструктором копій або неявно визначеним оператором призначення копії вважається застарілим
= default
загального існування , а не причинами для = default
конструктора проти роботи { }
.
{}
було вже особливість мови до початку введення =default
, ці причини цього неявно спираються на відмінності (наприклад , «немає коштів , щоб воскресити [пригніченим по замовчуванням] немає» означає , що {}
це НЕ еквівалентно за замовчуванням ).
Це справа семантики в деяких випадках. Це не дуже очевидно для конструкторів за замовчуванням, але це стає очевидним для інших функцій-членів, створених компілятором.
Для конструктора за замовчуванням можна було б зробити будь-який конструктор за замовчуванням з порожнім тілом вважатись кандидатом на отримання тривіального конструктора, як і для використання =default
. Зрештою, старі порожні конструктори за замовчуванням були легальними C ++ .
struct S {
int a;
S() {} // legal C++
};
Незалежно від того, чи компілятор розуміє, що цей конструктор є тривіальним, у більшості випадків поза оптимізаціями (ручними чи компіляторними) не має значення.
Однак ця спроба розглянути порожні функції функцій як "за замовчуванням" повністю розпадається на інші типи функцій-членів. Розглянемо конструктор копій:
struct S {
int a;
S() {}
S(const S&) {} // legal, but semantically wrong
};
У вищенаведеному випадку конструктор копій, написаний із порожнім корпусом, зараз неправильний . Це вже фактично нічого не копіює. Це зовсім інший набір семантики, ніж семантика конструктора копіювання за замовчуванням. Бажана поведінка вимагає, щоб ви написали якийсь код:
struct S {
int a;
S() {}
S(const S& src) : a(src.a) {} // fixed
};
Однак навіть у цьому простому випадку компілятору стає набагато більше тягаря, щоб переконатися, що конструктор копій ідентичний тому, який він створив би сам, або побачив, що конструктор копій є тривіальним (еквівалентний аmemcpy
, в основному ). Компілятор повинен перевірити кожен вираз ініціалізатора члена та переконатися, що він ідентичний виразу, щоб отримати доступ до відповідного члена джерела та нічого іншого, переконайтесь, що жодним членам не залишається тривіальна конструкція за замовчуванням тощо. компілятор буде використовувати для перевірки, що власні генеровані версії цієї функції є тривіальними.
Тоді розглянемо оператор присвоєння копії, який може отримати ще більш суєтний характер, особливо у нетривіальному випадку. Це тонна плита котла, яку вам не хочеться писати протягом багатьох класів, але вас все одно змушують на C ++ 03:
struct T {
std::shared_ptr<int> b;
T(); // the usual definitions
T(const T&);
T& operator=(const T& src) {
if (this != &src) // not actually needed for this simple example
b = src.b; // non-trivial operation
return *this;
};
Це простий випадок, але це вже більше коду, ніж ви коли-небудь хотіли б змусити писати для такого простого типу, як T
(особливо після того, як ми кинемо операції переміщення в суміш). Ми не можемо покластися на порожнє тіло, що означає "заповнити типові налаштування", оскільки порожнє тіло вже абсолютно дійсне і має чітке значення. Насправді, якби порожнє тіло використовувалося для позначення "заповнення за замовчуванням", тоді не було б способу явно зробити конструктор копіювання без використання або подібне.
Знову це питання послідовності. Порожнє тіло означає "не робити нічого", але для таких речей, як конструктори копій, ви насправді не хочете "не робити нічого", а навпаки, "робіть усі речі, які ви зазвичай робите, якщо їх не придушують". Звідси =default
. Це необхідно для подолання пригнічених функцій-членів, створених компілятором, таких як конструктори копіювання / переміщення та оператори призначення. Тоді просто "очевидно" змусити його працювати і для конструктора за замовчуванням.
Можливо, було б непогано зробити конструктор за замовчуванням з порожніми тілами та тривіальні конструктори членів / базових також вважатись тривіальним так само, як це було б, =default
якби тільки зробити старіший код більш оптимальним у деяких випадках, але більшість кодів низького рівня, що покладаються на тривіальні конструктори за замовчуванням для оптимізації також покладаються на тривіальні конструктори копій. Якщо вам доведеться запустити і "виправити" всі ваші старі конструктори копій, це дійсно не так багато, щоб виправити всі ваші старі конструктори за замовчуванням. Це також набагато чіткіше і очевидніше, використовуючи явне =default
позначення своїх намірів.
Є кілька інших речей, завдяки яким функції, створені компілятором, зроблять, що вам доведеться також явно вносити зміни в підтримку. Підтримка constexpr
конструкторів за замовчуванням - один із прикладів. Це просто простіше подумки, =default
ніж позначати функції усіма іншими спеціальними ключовими словами та такими, які маються на увазі, =default
і це було однією з тем C ++ 11: полегшити мову. У ньому все ще багато бородавок і компромісів, які повертаються назад, але зрозуміло, що це великий крок вперед від C ++ 03, коли справа стосується простоти у використанні.
= default
що зробить, a=0;
а ні! Мені довелося відмовитись на користь : a(0)
. Я все ще плутаю, наскільки корисне = default
тхо, це про продуктивність? це десь зламається, якщо я просто не користуюся = default
? Я спробував прочитати всі відповіді тут, купити, я новачок у деяких c ++ речах, і у мене виникають багато проблем з його розумінням.
a=0
приклад - через поведінку тривіальних типів, які є окремою (хоч і пов’язаною) темою.
= default
і все ще a
буде грант =0
? якимось чином? ви думаєте, я міг створити нове запитання на кшталт "як створити конструктор = default
і надавати поля буде належним чином ініціалізовано?", btw У мене виникла проблема з a, struct
а не class
, і додаток працює правильно навіть не використовуючи = default
, я можу додайте мінімальну структуру в цьому питанні, якщо вона хороша :)
struct { int a = 0; };
Якщо ви вирішите, що вам потрібен конструктор, ви можете встановити його за замовчуванням, але зауважте, що тип не буде тривіальним (що добре).
Через депресію std::is_pod
та її альтернативу std::is_trivial && std::is_standard_layout
фрагмент відповіді @JosephMansfield стає:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() {}
};
int main() {
static_assert(std::is_trivial_v<X>, "X should be trivial");
static_assert(std::is_standard_layout_v<X>, "X should be standard layout");
static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}
Зауважте, що Y
досі стандартний макет.
default
це не нове ключове слово, це лише нове використання вже зарезервованого ключового слова.