@SebastianRedl вже дав прості, прямі відповіді, але деякі додаткові пояснення можуть бути корисні.
TL; DR = є правило стилю для спрощення конструкторів простим, для цього є причини, але ці причини в основному стосуються історичного (або просто поганого) стилю кодування. Поводження з винятками в конструкторах чітко визначено, і деструктори все ще будуть викликатися повністю сконструйованими локальними змінними та членами, а це означає, що в ідіоматичному коді C ++ не повинно виникнути жодних проблем. Правило стилю так чи інакше зберігається, але зазвичай це не проблема - не вся ініціалізація повинна бути в конструкторі, особливо не обов'язково в цьому конструкторі.
Це загальне правило стилю, що конструктори повинні робити абсолютний мінімум, який вони можуть, щоб створити визначений дійсний стан. Якщо ваша ініціалізація є більш складною, її слід обробляти поза конструктором. Якщо значення, яке може створити ваш конструктор, не має дешевого для ініціалізації, вам слід послабити інваранти, що застосовуються вашим класом, щоб додати його. Наприклад, якщо виділення пам’яті для вашого класу для управління занадто дороге, додайте ще не виділене ще нульове стан, оскільки, звичайно, такі особливі регістри, як null, ніколи нікому не викликали жодних проблем. Ахм.
Хоча це звичайно, але, безумовно, в цій крайній формі він дуже далеко не абсолютний. Зокрема, як свідчить мій сарказм, я перебуваю в таборі, який каже, що ослаблення інваріантів майже завжди є надто високою ціною. Тим не менш, є причини, що стоять за правилом стилю, і є способи мати як мінімальні конструктори, так і сильні інваріанти.
Причини пов'язані з автоматичним очищенням деструктора, особливо перед винятками. В основному, має бути чітко визначений момент, коли компілятор несе відповідальність за виклик деструкторів. Поки ви все ще перебуваєте у виклику конструктора, об'єкт не обов'язково повністю побудований, тому невірно викликати деструктор для цього об’єкта. Тому відповідальність за знищення об'єкта перекладається на компілятор лише тоді, коли конструктор успішно завершить. Це відоме як RAII (виділення ресурсів - ініціалізація), що насправді не найкраще ім'я.
Якщо викид винятку відбувається всередині конструктора, все, що побудовано частково, має бути чітко очищено, як правило, в а try .. catch
.
Однак компоненти об'єкта, які вже успішно сконструйовані, є відповідальністю за компілятори. Це означає, що на практиці це насправді не велика справа. напр
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
Корпус цього конструктора порожній. До тих пір , як конструктори base1
, member2
і member3
є безпечними винятком, немає нічого страшного. Наприклад, якщо конструктор member2
кидає, той конструктор відповідає за очищення. База base1
вже була повністю побудована, тому її деструктор буде автоматично викликаний. member3
ніколи навіть частково не будувався, тому не потребує очищення.
Навіть коли є тіло, локальні змінні, які були повністю побудовані до викиду винятку, будуть автоматично знищені, як і будь-яка інша функція. Органи конструктора, що перемикають необроблені покажчики або "володіють" якимось неявним станом (зберігається в іншому місці) - як правило, означає, що виклик функції "почати / придбати" повинен відповідати виклику "кінець / випуск" - може спричинити проблеми безпеки виключення, але справжня проблема там не вдається правильно керувати ресурсом через клас. Наприклад, якщо замінити необроблені покажчики unique_ptr
на в конструкторі, деструктор для unique_ptr
буде викликаний автоматично, якщо це потрібно.
Є ще інші причини, що люди віддають перевагу конструкторам "мінімальних". Одне це просто тому, що існує правило стилю, багато людей вважають, що виклики конструктора дешеві. Один із способів зробити це, але все ще мають сильні інваріанти, - це мати окремий заводський / будівельний клас, який замість цього має ослаблені інваріанти і який встановлює необхідне початкове значення, використовуючи (можливо, багато) звичайні виклики функцій членів. Після того, як у вас є початковий стан, який вам потрібен, передайте цей об'єкт як аргумент конструктору для класу із сильними інваріантами. Це може "вкрасти кишки" об'єкта слабких інваріантів - перемістити семантику - що є дешевою (і зазвичай noexcept
) операцією.
І звичайно, ви можете зафіксувати це у make_whatever ()
функції, тож абонентам цієї функції ніколи не потрібно бачити екземпляр класу ослаблених інваріантів.