Елемент 49 Разберете новото поведение на манипулатора - Ефективно използване на C
return ::оператор new(size);
// currentHandler във всеки клас се инициализира на null
std::new_handler NewHandlerSupport ::currentHandler = 0;
С този шаблон на клас добавянето на поддръжка на set_new_handler към Widget е много лесно: Widget просто наследява от NewHandlerSupport. (Това може да звучи екстравагантно, но ще обясня подробно какво се случва тук по-долу.)
клас Widget: публичен NewHandlerSupport
. // както преди, но без декларация
>; // set_new_handler или оператор new
Това е всичко, което трябва да се направи в класа Widget, за да се осигури специфичен за класа манипулатор set_new_handler.
Но може би се притеснявате от факта, че Widget наследява от класа New-HandlerSupport? Ако е така, тогава вашето безпокойство ще се увеличи, когато забележите, че NewHandlerSupport никога не използва параметъра си за тип T. Той не се нуждае от него. Единственото нещо, от което се нуждаем, е отделно копие на NewHandlerSupport или по-скоро неговият статичен член currentHandler за всеки клас, извлечен от NewHandlerSupport. Параметърът T на шаблона просто разграничава един подклас от друг. Механизмът на шаблона автоматично генерира копие на currentHandler за всеки тип T, за който е създаден.
Що се отнася до факта, че Widget наследява от шаблонен базов клас, който приема Widget като параметър на типа, не се тревожете, просто изглежда странно в началото. На практика това е много удобна техника, която има собствено име, което отразява факта, че не изглежда естествена за всеки, който я вижда за първи път. И се нарича любопитен рекурсивен шаблонен шаблон (любопитен повтарящ се шаблонен шаблон - CRTP). Честно казано.
Шаблони като NewHandlerSupport улесняват добавянето на персонализираниза новия клас манипулатор към всеки клас, който се нуждае от него. Въпреки това, наследяването на прикачен клас води до множествено наследяване и преди да тръгнете по този път, може да искате да прочетете отново точка 40.
Преди 1993 г. C++ изискваше новият оператор да върне нулев указател, ако не можеше да разпредели необходимата памет. Сега операторът new трябва да хвърли bad_alloc в този случай. Но беше написано огромно количество C++ код, преди компилаторите да започнат да поддържат новата спецификация. Комитетът по стандартите на C++ не искаше да премахне целия код, базиран на нула, така че беше решено да се осигурят алтернативни форми на оператор new, които осигуряват традиционното поведение на връщане на нула при повреда. Тези форми са известни като "nothrow" (не хвърлят изключения), защото използват nothrow обекти (дефинирани в заглавката) в точката, където се използва new:
// заделянето на памет е неуспешно
if(pw1 == 0) . // тази проверка трябва
// паметта е неуспешна
if(pw2 == 0) . // тази проверка може
Операторът без хвърляне new предоставя по-малко стабилни гаранции за изключения, отколкото може да изглежда на пръв поглед. Две неща се случват в израза "new (std::nothrow)Widget". Първо, nothrow версията на new се извиква, за да разпредели достатъчно памет за съхранение на обекта Widget. Ако не беше възможно да се получи паметта, тогава новият оператор връща нулев указател, както искахме. Ако паметта е била разпределена, тогава се извиква конструкторът на Widget и в този момент всички гаранции приключват. Конструкторът на Widget може да направи всичко. Той може сам да поиска малко памет с new и ако го направи, никой не го принуждава да използва nothrow.Докато извикването на новия оператор на "new (std::nothrow)Widget" не хвърля изключение, то не го прави и на конструктора на Widget. И ако хвърли изключение, ще се разпространи както обикновено. Заключение? Използването на nothrow new само гарантира, че дадения оператор new няма да хвърли изключение, но не дава гаранция за израз като "new (std::nothrow)Widget". Следователно едва ли си струва да се прибягва до нехвърляне на нови изобщо.
Независимо дали използвате "нормалния" (хвърля) нов или варианта nothrow, важно е да разберете поведението на новия манипулатор, тъй като той се извиква и от двете форми.