С. Мейерс - Эффективный и современный C++ (1114942), страница 25
Текст из файла (страница 25)
Если вы хотите, чтобы компиляторы гарантировали, что переменная имеет значение, которое можно использовать в требующих константы времени компиляции контекстах, то следует использовать constexpr, а не const .Сценарии применения объектов const expr становятся более интересными, когдав дело вступают функции constexpr. Такие функции производят константы времени106Глава 3. Переход к современному С++компиляции, когда они вызь1ваются с константами времени компиляции. Если они вызываются со значениями, неизвестными до времени выполнения, они производят значения времени выполнения. Это может выглядеть так, как будто вы не знаете, что они будутделать, но так думать - неверно. Вот как выглядит правильный взгляд на эти моменты:•Функции, объявленные как const expr, могут использоваться в контекстах, требующих константы времени компиляции.
Если значения передаваемых вами аргументов в соnstехрr-функцию в таком контексте известны во время компиляции,результат функции будет вычислен в процессе компиляции. Если любое из значений аргументов неизвестно во время компиляции, ваш код будет отвергнут.•Когда соnstехрr-функция вызывается с одним или несколькими значениями, неизвестными во время компиляции, она действует так же, как и обычная функция,выполняя вычисления во время выполнения.
Это означает, что вам не нужны двефункции для выполнения одних и тех же операций, одной - для констант времени компиляции, другой - для всех прочих значений. Функция, объявленная какconst expr, выполняет их все.Предположим, что нам нужна структура данных для хранения результатов эксперимента, который может быть проведен при разных условиях. Например, уровень освещения в ходе эксперимента может быть высоким, низким или освещение может бытьотключено вовсе; может быть разная температура, и т.д. Если всего имеется n условий,влияющих на проведение эксперимента, и у каждого по три возможных состояния, то общее количество комбинаций составит 3".
Хранение результатов экспериментов для всехкомбинаций условий требует структуры данных с достаточным количеством памятидля хранения 3" значений. В предположении, что каждый результат представляет собойint и что n известно (или может быть вычислено) во время компиляции, подходящимвыбором структуры данных может быть std : : a rray. Однако нам требуется способ вычисления 3" во время компиляции. Стандартная библиотека С++ предоставляет функциюstd : : pow, обеспечивающую интересующую нас математическую функциональность, нос точки зрения наших целей имеются две проблемы. Во-первых, std : : pow работает с типами с плавающей точкой, а нам нужен целочисленный результат.
Во-вторых, std : : powне является constexpr (т.е. не гарантирует возврат времени компиляции при переданныхей значениях времени компиляции), так что мы не можем использовать ее для указанияразмера std : : a rray.К счастью, мы можем написать функцию pow, которая нам нужна. Как это сделать,я покажу чуть позже, но сначала давайте взглянем, каким образом эта функция можетбыть объявлена и использована:/ / pow является constexprconstexprint pow ( int base, int ехр ) noexcept // Не генерирует исключений! / Ее реализация - ниже3 .9. Испопьэуйте, rде это воэможно, constexpr107constexpr auto nшnConds=5;std : : array<int, роw (З , numConds) >results ;11 Количество условий11 results содержит// з лnшnConds элементовВспомним, что constexpr перед pow не говорит о том, что pow возвращает константное значение; оно говорит, что если base и ехр являются константами времени компиляции, то результат pow может быть использован как константа времени компиляции.
Еслиbase и/или ехр не являются константами времени компиляции, то результат pow будетвычисляться во время выполнения. Это означает, что pow может быть вызвана не толькодля вычисления во время компиляции таких вещей, как размер std : : array, но и в контексте времени выполнения, как здесь:11auto base = readFromDB ( "base" ) ;auto ехр = readFromDB ( "exponent " ) ; 1111auto baseToExp = pow (Ьase , ехр) ;11Эти значения получаютсяво время компиляцииВызов функции powво время вьmолненияПоскольку функции constexpr должны быть способны возвращать результаты вовремя компиляции при вызове со значениями времени компиляции, на их реализациинакладываются ограничения. Эти ограничения различны в C++ l l и С++ 1 4.В С++ 1 1 функции constexpr могут содержать не более одной выполнимой инструкции - return.
Это выглядит более ограничивающим, чем является на самом деле, поскольку для повышения выразительности соnstехрr-функций можно использовать двехитрости. Во-первых, можно применять условный оператор " ? : " вместо инструкцииi f- e l se, а во-вторых, вместо циклов можно использовать рекурсию. Таким образом,функция pow может быть реализована следующим образом:constexpr int pow ( int base, int ехр ) noexcept{return (ехр == О ? 1 : base * pow ( base, ехр-1) ) ;Этот код работает, но только очень непритязательный функциональный программистсможет назвать его красивым.
В С++ 14 ограничения на соnst ехрr-функции существеннослабее, так что становится возможной следующая реализация:constexpr i nt pow ( int base , int ехр ) noexcept // С++ 1 4auto result1;for ( int i = О ; i < ехр ; ++i ) result * = base ;return result;=Функции constexpr ограничены приемом и возвратом только литеральных типов(literal types), которые, по сути, означают типы, могущие иметь значения, определяемыево время компиляции. В С++ 1 1 к н им относятся все встроенные типы за исключением108Глава 3.
Переход к современному С++void, но литеральными могут быть и пользовательские типы, поскольку конструкторыи прочие функции-члены также могут являться constexpr:class Point {puЬli c :constexpr Point (douЫe xValО , douЫe yVal0) noexcept: x (xVal ) , y ( yVal ){}constexpr douЫe xValue ( ) const noexceptconstexpr douЫe yVa lue ( ) const noexceptvoid setX (douЫe newX) noexceptvoid setY ( douЫe newY) noexceptprivate :douЫe х, у;};return х ;return у ;х = newX;уnewY;Здесь конструктор Point может быть объявлен как constexpr, поскольку, если переданные ему аргументы известны во время компиляции, значения членов-данных созданного Point также могут быть известны во время компиляции.
А значит, инициализированный таким образом объект Point может быть const expr:constexpr Point р1 ( 9 . 4 , 27 . 7 ) ; // ОК, во время компиляции11 работает constexpr конструкторconstexpr Point р2 ( 2 8 . 8 , 5 . 3 ) ; / / То же самоеАналогично функции доступа xVa lue и yVa lue могут быть const expr, поскольку еслиони вызываются для объекта Point со значением, известным во время компиляции (например, объект constexpr Poi nt), значения членов-данных х и у могут быть известныво время компиляции. Это делает возможным написать соnstехрr-функции, которыевызывают функции доступа Point и инициализируют соnstехрr-объекты результатамиВЫЗОВОВ ЭТИХ функций:constexprPoint midpoint ( const Point& p l , const Point& р2 ) noexceptreturn { (pl .
xValue ( ) +p2 . xValue ( ) ) /2 , / / ВьGов constexpr(pl .yValue ( ) +p2 .yValue ( ) ) /2 } ; / / функции - члена/ / Инициализация= midpoint ( p l , р2 ) ;11 constexpr объекта результатом соnstехрr - функцииconstexpr auto midЭто очень интересно. Это означает, что объект mid может быть создан в памяти, предназначенной только для чтения, несмотря на то что его инициализация включает вызовыконструкторов, функций доступа и функции, не являющейся членом! Это означает, чтовы можете использовать выражение наподобие mi d .
xVa 1 ue ( } * 1 О в аргументе шаблона3 .9 . Испо л ьзуйте, где это возможно, constexpr1 09или в выражении, определяющем значение перечислителя•! Это означает, что традиционно довольно строгая граница между работой во время компиляции и работой во времявыполнения начинает размываться, и некоторые вычисления, традиционно являющиесявычислениями времени выполнения, могут перейти на стадию компиляции. Чем больший код участвует в таком переходе, тем быстрее будет работать ваша программа. (Однако компилироваться она может существенно дольше.)В С++ 1 1 два ограничения предотвращают объявление функций-членов Poiпt s et Xи s e t Y как conste xpr.
Во-первых, они модифицируют объект, с которым работают, а вС++ 1 1 функции-члены con s texpr неявно являются con s t . Во-вторых, они имеют возвращаемый тип void, а void не является литеральным типом в С++ 1 1 . Оба эти ограничениясняты в С++ 1 4, так что в С++1 4 даже функции установки полей Po i nt могут быть объявлены как con s t expr:class PointpuЫ i c :constexpr void setX (douЫe пеwХ) поехсерt / / С++ 1 4newX; }{ х=constexpr void setY (douЫe пеwУ) поехсерt // С++ 1 4{ у = newY; )};Это делает возможным написание функций наподобие следующей:11 Возвращает отражение точки р/ / относительно начала координат (С++1 4 )constexpr Poiпt reflectioп ( const Point& р ) noexceptPoiпt result;resul t .
setX ( -p . xValue ( ) ) ;result . setY ( -p . yValue ( ) ) ;returп result ;/ / Неконстантный объект Point/ / Установка его полей х и у/ / Возврат копииСоответствующий клиентский код имеет вид:constexprconstexprcoпstexprconstexpr•Point р 1 ( 9 . 4 , 27 . 7 ) ; 11 Как и вьnuеPoint р2 ( 2 8 .
8 , 5 . 3 ) ;auto midmidpoiпt (pl , р2 ) ;11 re flectedМid представляетauto re flectedМid==Поскольку P o i n t : : xValue возвращает douЫe, типом m i d . xValue ( ) * 1 0 также является douЫe.Типы с плавающей точкой не могут использоваться для инстанцирования шаблонов или для указания значений перечислений, но они могут быть использованы как части больших выражений,дающих интегральные типы. Например, для и нстанцирования шаблона или для указания значения перечислителя может использоваться выражение s t a t i c_c a s t < i n t > (mi d .
xVa lue ( ) * 1 0 ) .110Гn ава 3 . Переход к современному Cttreflection ( mid) ;11 собой ( - 1 9 . 1 - 1 6 . 5 ) и11 известно во время компиляцииСовет из этого раздела заключается в том, чтобы использовать constexpr везде, гдеэто только возможно, и теперь, надеюсь, вам понятно, почему: и объекты const expr,и соnstехрr-функции могут применяться в более широком диапазоне контекстов, чемобъекты и функции, не являющиеся constexpr. Применяя constexpr, где это возможно,вы максимизируете диапазон ситуаций, в которых ваши объекты и функции могут бытьиспользованы.Важно отметить, что constexpr является частью интерфейса объекта или функции.const e xpr провозглашает: "Меня можно использовать в контексте, где для С++ требуется константное выражение". Если вы объявляете объект или функцию как constexpr,клиенты могут использовать их в указанных контекстах. Если позже вы решите, чтотакое использование constexpr было ошибкой, и удалите его, то это может привестик тому, что большое количество клиентского кода перестанет компилироваться.