Оператор static_cast

Статические методы не имеют указателя *this

У статических методов есть две интересные особенности.

Во-первых, поскольку статические методы не привязаны к объекту, то они не имеют скрытого указателя *this! Здесь есть смысл, так как указатель *this всегда указывает на объект, с которым работает метод. Статические методы могут не работать через объект, поэтому и указатель *this не нужен.

Во-вторых, статические методы могут напрямую обращаться к другим статическим членам (переменным или функциям), но не могут напрямую обращаться к нестатическим членам. Это связано с тем, что нестатические члены принадлежат объекту класса, а статические методы — нет!

Ключевое слово final

В заключении
этого занятия я расскажу об еще одном ключевом слове final. Оно позволяет
задавать константы в языке Java. Например, если у поля cnt добавить это
ключевое слово, то дальнейшее изменение переменной cnt станет
невозможным:

private static final int cnt = ;

Опять же,
обратите внимание на очередность ключевых слов. Конечно, это странный пример,
поэтому давайте оставим переменную cnt как обычную
статическую, а в класс Point добавим еще одно
поле с указанием final:

final int MAX_COORD = 10;

Теперь,
переменная MAX_COORD является
константой и не может быть изменена. Мало того, мы должны таким переменным
сразу присваивать определенное значение, то есть, записывать их вот так нельзя:

final int MAX_COORD;

Ключевое слово final можно
использовать и у методов. Но особенность их поведения будет проявляться только
в момент наследования. Поэтому я буду говорить об этом в теме наследования
классов.

Путь кодера

Подвиг 1. Объявите класс ShopItem для
представления продуктов в магазине с полями: id (идентификатор
– целое число), название товара, габариты, вес, цена. Поле id должно быть
уникальным для каждого объекта класса. Это следует реализовать через
статическую переменную, которая подсчитывает количество создаваемых
экземпляров.

Подвиг 2. Реализовать
класс Rect для описания
прямоугольника с полями: x1, y1, x2, y2 – координат
вершин верхнего правого и нижнего левого углов. Прописать два статических
метода для вычисления ширины и высоты прямоугольника. В качестве параметра этим
методам передавать ссылку на экземпляр класса Rect, для которого
выполняется вычисление.

Подвиг 3. Реализовать
класс Singleton, в котором
определить статический метод getInstance(). Этот метод
должен возвращать экземпляр класса, если он еще не создавался. Иначе,
возвращается ссылка на ранее созданный экземпляр. Также следует запретить
создание объектов класса Singleton напрямую через оператор new. (Полученная
реализация будет гарантировать существование только одного экземпляра класса в
процессе работы программы и, фактически, является примером известного паттерна singleton).

Видео по теме

#11 Концепция объектно-ориентированного программирования (ООП)

#12 Классы и создание объектов классов

#13 Конструкторы, ключевое слово this, инициализаторы

#14 Методы класса, сеттеры и геттеры, public, private, protected

#15 Пакеты, модификаторы конструкторов и классов

#16 Ключевые слова static и final

#17 Внутренние и вложенные классы

#18 Как делается наследование классов

#19 Ключевое слово super, оператор instanceof

#20 Модификаторы private и protected, переопределение методов, полиморфизм

#21 Абстрактные классы и методы

#22 Интерфейсы — объявление и применение

#23 Интерфейсы — приватные, статические и дефолтные методы, наследование интерфейсов

#24 Анонимные внутренние классы

#25 Перечисления (enum)

#26 Обобщения классов (Generics)

#27 Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

#28 Обобщенные интерфейсы, наследование обобщенных классов

Сфера

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

Пример

Пример статической локальной переменной в C:

#include <stdio.h>

void Func() {
  static int x = ;
  // |x| is initialized only once across five calls of |Func| and the variable
  // will get incremented five times after these calls. The final value of |x|
  // will be 5.
  x++;
  printf("%d\n", x);  // outputs the value of |x|
}

int main() {
  Func();  // prints 1
  Func();  // prints 2
  Func();  // prints 3
  Func();  // prints 4
  Func();  // prints 5

  return ;
}

Последовательность шагов и уже готовая шаблонная магия

Итак, нам нужно иметь класс с несколькими наборами методов. Содержимое этих наборов должно откуда-то взяться. Откуда?

В языке D мы могли бы воспользоваться и определить разные части класса в зависимости от разных условий. В каком-нибудь Ruby мы могли бы подмешать методы в свой класс посредством метода include. Но мы в C++, в котором пока наши возможности сильно ограничены: мы можем либо определить метод/атрибут прямо внутри класса, либо можем унаследовать метод/атрибут из какого-то базового класса.

Определить разные методы/атрибуты внутри класса в зависимости от какого-то условия мы не можем, т.к. C++ный — это не D-шный . Следовательно, остается только наследование.

В C++ мы можем определить несколько базовых классов, от которых мы затем отнаследуем . А выбор того или иного базового класса уже будем делать в зависимости от значений параметров шаблона, посредством std::conditional.

Но фокус в том, что нам потребуется не просто набор базовых классов, а небольшая цепочка наследования. В ее начале будет класс, который будет определять общую функциональность, которая потребуется в любом случае. Далее будут базовые классы, которые будут определять логику поведения «умного указателя». А уже затем будет класс, который определит нужные getter-ы. В таком порядке мы и рассмотрим реализованные классы.

Нашу задачу упрощает то, что в SObjectizer-е уже есть готовая шаблонная магия, , а также . Поэтому в реализации мы эту готовую магию будем просто использовать и не станем погружаться в детали ее работы.

Статические классы

Статические вложенные классы можно использовать вне своего родительского класса. Если у такого класса стоит модификатор доступа public, его можно использовать в любом месте программы. Такие классы фактически ничем не отличаются от любого обычного класса. Хотя есть пара отличий.

Имя класса

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

Примеры:

Класс родитель Вложенный класс Полное имя вложенного класса

Если вложенный класс имеет свой вложенный класс, их имена просто склеиваются через точку.

Типичный пример вложенного класса в JDK — это класс Entry внутри класса Map. Если вы хотите получить множество пар элементов из объекта , то метод вернет вам множество пар типа .

Вот запись — это и есть внешний и внутренний классы.

Создание объекта

Создать объект вложенного статического класса очень легко. Выглядит это так:

Все, как и с обычными классами, только имя двойное.

Обращение к статическим методам

Если у статического класса есть статические методы, обращаться к ним можно точно так же, как к статическим методам обычных классов (только имя класса теперь двойное).

Обращение к статическим переменным

Обращаться к публичным статическим переменным вложенного класса тоже легко:

Static Data Member in Class

Static data members of class are those members which are shared by all the objects. Static data member has a single piece of storage, and is not available as separate copy with each object, like other non-static data members.

Static member variables (data members) are not initialied using constructor, because these are not dependent on object initialization.

Also, it must be initialized explicitly, always outside the class. If not initialized, Linker will give error.

1

Once the definition for data member is made, user cannot redefine it. Though, arithmetic operations can be performed on it.

Статические vs Обычные методы

Чем же отличаются статические методы от обычных?

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

Отличия двух типов методов можно выразить в таблице:

Способность Обычный метод Статический метод
Есть связь с экземпляром класса Да Нет
Может вызывать обычные методы класса Да Нет
Может вызывать статические методы класса Да Да
Может обращаться к обычным переменным класса Да Нет
Может обращаться к статическим переменным класса Да Да
Может быть вызван у объекта Да Да
Может быть вызван у класса Нет Да

Зачем такие методы нужны, если они так сильно ограничены? Все дело в том, что у такого подхода тоже есть свои преимущества.

Во-первых, чтобы обратиться к статическим методам и переменным, не надо передавать никакую ссылку на объект.

Во-вторых, иногда бывает нужно, чтобы переменная была в единственном экземпляре. Как, например, переменная (статическая переменная out класса System).

И, в-третьих, иногда нужно вызвать метод еще до того, как будет возможность создавать какие-то объекты. Например, вызов метода main(), с которого начинается выполнение программы: его вызывает Java-машина до создания экземпляра класса.

Есть связь с экземпляром класса

При вызове обычного метода в него передается скрытый параметр — объект, у которого его вызывали. Этот параметр имеет имя . Именно этот скрытый параметр — ссылка на объект, у которого вызвали метод — и отличает обычные методы от статических.

У статических методов такого скрытого параметра нет, поэтому внутри статических методов нельзя пользоваться ключевым словом , и из статического метода нельзя вызвать нестатический: ссылку на экземпляр класса попросту неоткуда взять.

Может вызывать обычные методы класса

В обычном методе класса всегда есть скрытый параметр — — ссылка на объект класса, у которого был вызван метод. Каждый раз, когда вы вызываете обычный метод внутри другого обычного метода, для этого вызова используется скрытый параметр . Пример

Код Как оно работает

Именно поэтому нельзя вызвать обычный метод из статического. Внутри статического метода просто нет скрытой переменной с именем .

Ну или представьте другой случай: в программе еще не создан ни один объект нашего класса. Статический метод класса можно вызвать? Да. А сможет этот статический метод вызвать обычный метод?

И у какого объекта он его вызовет? Ведь еще не существует ни одного экземпляра нашего класса!

Может вызывать статические методы класса

Статические методы можно вызывать откуда угодно — из любого места программы. А значит, их можно вызывать и из статических методов, и из обычных. Никаких ограничений тут нет.

Может обращаться к обычным переменным класса

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

Статический метод не знает, из какого экземпляра класса ему брать значения обычных переменных. У нас вообще легко может быть ситуация, когда статический метод вызван, а ни одного экземпляра класса еще создано в программе не было.

Поэтому статические методы не могут обращаться к обычным переменным класса.

Статический метод вызывает обычный метод, вот только у какого объекта он должен вызваться?

Неизвестно! Поэтому и нельзя вызывать обычный метод из статического, не указывая ссылку на объект!

Может обращаться к статическим переменным класса

Ситуация с обращением к статическим переменным такая же, как и с обращениями к статическим методам. К статическим переменным можно обращаться из любого места в программе. А значит, можно обращаться из статических и обычных методов.

Может быть вызван у объекта

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

Код Как его видит компилятор

Может быть вызван у класса

У класса можно вызвать только статический метод, для вызова обычного метода нужна ссылка на экземпляр класса. Поэтому нельзя вызвать обычный метод конструкцией вида

Когда использовать каждый из методов?

Выбор того, какой из методов использовать, может показаться достаточно сложным. Тем не менее с опытом этот выбор делать гораздо проще.

Чаще всего метод класса используется тогда, когда нужен генерирующий метод, возвращающий объект класса. Как видим, метод класса используется для создания объекта класса по году рождения, а не возрасту. 

Статические методы в основном используются как вспомогательные функции и работают с данными, которые им передаются.

Запомнить

  • Методы экземпляра класса получают доступ к объекту класса через параметр и к классу через .
  • Методы класса не могут получить доступ к определённому объекту класса, но имеют доступ к самому классу через .
  • Статические методы работают как обычные функции, но принадлежат области имён класса. Они не имеют доступа ни к самому классу, ни к его экземплярам.
  • Даже если вы программируете только ради интереса, изучение ООП в Python поможет писать код так, чтобы в дальнейшем было легче искать ошибки и использовать его повторно.
  • Давайте синхронизировать потоки в Python
  • Овладей Python, создавая реальные приложения. Часть 1
  • Полезные хитрости на Python от А до Я
  • Изучение Python: от нуля до мастера

Перевод статьи Rachit TayalPython’s Instance, Class and Static Methods

Статические методы

Похожим образом
ведут себя и статические методы классов: они принадлежат непосредственно классу
и не дублируются в его экземпляры. Давайте для примера добавим статический
метод в класс Point:

class Point {
    private static int cnt = ;
    int x, y;
 
    ...
 
    public static int getCounter() {
        return cnt;
    }
}

Мы здесь сделали
поле cnt приватным, а
доступ к нему – через геттер getCounter

И обратите внимание в какой
последовательности записываются модификаторы доступа, слово static и тип поля или метода:. static

static <тип> <имя
поля или метода>

Соответственно,
в функции main мы теперь
должны прописать этот геттер вместо прямого обращения к cnt:

System.out.println(Point.getCounter());

В итоге картина
расположения статических и нестатических полей будет следующей:

Здесь
статический метод getCounter() общий для всех экземпляров класса Point. Фактически, он
принадлежит только классу Point, но также может вызываться из его
объектов. Именно поэтому, мы можем обращаться к нему непосредственно через
класс Point, используя
конструкцию:

Point.getCounter()

Так можно делать
с любыми статическими методами и полями класса. Но статичность налагает и определенные
ограничения. В частности, метод getCounter() может получать доступ к
статической переменной cnt, но не может работать с динамическими
полями x, y. Действительно,
если написать что-то вроде:

    public static int getCounter() {
        return x;
    }

то возникнет
ошибка, так как статический метод на уровне класса просто «не видит» переменные
x, y в экземплярах.
Все что ему доступно – это другие статические методы и поля класса Point. То есть, из
статических методов можно вызывать только другие статические методы и
обращаться исключительно к статическим переменным. Или же, создавать объекты и
переменные непосредственно внутри статического метода:

    public static int getCounter() {
        int x = 5;
        return x;
    }

Отчасти это мы
делаем в статическом методе main().

Решение

когда повышен до в интегральных акциях отрицательные значения также теряются (что приводит к такому веселью, как быть правдой).

Как и большинство механизмов в C (которые наследуются в C ++), обычные арифметические преобразования следует понимать с точки зрения аппаратных операций. Создатели C были очень хорошо знакомы с языком ассемблера машин, с которыми они работали, и они написали C, чтобы иметь непосредственный смысл для себя и людей, подобных себе, при написании вещей, которые до этого были бы написаны на ассемблере (например, UNIX). ядро).

Теперь процессоры, как правило, не имеют инструкций смешанного типа (добавьте float к double, сравните int с float и т. Д.), Потому что это будет огромной тратой недвижимости на пластине — вам придется реализовать столько опкодов, сколько вы хотите, чтобы поддерживать различные типы. То, что у вас есть только инструкции для «добавить int к int», «сравнить float с float», «умножить без знака на unsigned» и т. Д., Делает обычные арифметические преобразования необходимыми в первую очередь — они представляют собой отображение двух типов на инструкцию семья, которая имеет больше всего смысла использовать с ними.

С точки зрения того, кто привык писать низкоуровневый машинный код, если у вас смешанные типы, инструкции на ассемблере, которые вы, скорее всего, рассмотрите в общем случае, — это инструкции, которые требуют наименьшего количества преобразований. Это особенно относится к плавающим точкам, где преобразования требуют больших затрат времени, особенно в начале 1970-х годов, когда разрабатывался C, компьютеры работали медленно и когда вычисления с плавающей точкой выполнялись программно. Это показано в обычных арифметических преобразованиях — только один операнд когда-либо был преобразован (за единственным исключением: /, где может быть преобразован в , что не требует ничего делать на большинстве машин. Возможно, не в любом месте, где применяется исключение).

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

Интересно отметить в этом контексте, кстати, что выше в иерархии, чем так что сравнивая с закончится в неподписанном сравнении (отсюда немного с самого начала). Я подозреваю, что это показатель того, что люди в старину считали меньше как ограничение на чем как расширение его диапазона значений: нам сейчас не нужен знак, поэтому давайте используем дополнительный бит для большего диапазона значений. Вы бы использовали его, если бы у вас были основания ожидать, что переполнится — гораздо большее беспокойство в мире 16-бит s.

68

Методы класса

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

Метод класса — это блок кода, состоящий из ряда инструкций, который можно вызывать по его имени. Он обязательно содержит возвращаемый тип, название, аргументы и тело метода.

Синтаксис метода в Java:

Строка возвращаемыйТип показывает, какого типа данные вернёт метод. Например, если в качестве возвращаемого типа мы поставим тип String, то метод должен будет вернуть строку, а если int — целое число.

Чтобы вернуть значение из метода, используется специальное слово return. Если мы хотим, чтобы метод ничего не возвращал, то вместо возвращаемого типа нужно использовать специальное слово void.

Аргументы — то, что нужно передать в метод при его вызове. Мы можем указать сколько угодно параметров через запятую либо не указывать ни одного.

Для примера напишем простейший метод с именем sum (пока что не в нашем классе Pet), который складывает два переданных числа и возвращает их результат:

Возвращаемый тип метода int, он указан перед именем sum. Далее идут два аргумента a и b, у обоих также указан тип int

Важно помнить, что возвращаемый тип и тип переменных не обязательно должны совпадать

Аргументы метода работают как обычные переменные — за пределами метода к ним никак нельзя получить доступ. Внутри метода мы складываем значения из переменных a и b, записываем полученное значение в переменную c. После этого мы возвращаем значение переменной c — только оно доступно вне метода.

Вот пример:

Мы передали в метод sum два значения 1 и 2, а на выходе получили результат их сложения 3. Также можно создать метод, который принимает значение типа String, а возвращает длину этой строки:

В этом случае у нас возвращаемый типа int, а параметр str — типа String.

Попробуем использовать этот метод:

Также мы можем создать метод, который ничего не возвращает, а просто печатает переданное слово в консоль:

Либо метод, который ничего не принимает на вход, а просто печатает «Привет!»:

В методах, которые ничего не возвращают, слово return можно опустить.

Обратите внимание, что return полностью прекращает выполнение метода:

Теперь попробуем вызвать этот метод, передав в него число 3:

В этом случае мы ничего не увидим в консоли, так как 3 меньше 5, а значит, отработает блок if и произойдёт выход из метода с помощью слова return.

Но если передадим 6, увидим нашу надпись «Привет!»:

Инициализация статических полей

Чтобы инициализировать статическое поле в C# можно использовать такие варианты:

Например,как мы это сделали в нашем классе:

static int ordNumber = 0;

Использование статического конструктора

Наряду с обычными конструкторами в языке C# могут использоваться статические конструкторы, т.е конструкторы, имеющие модификатор . Такие конструкторы обладают следующими особенностями:

  1. статический конструктор нельзя перегрузить
  2. статический конструктор не принимает никаких параметров
  3. статический конструктор выполняется один раз вне зависимости от того сколько объектов данного класса создано
  4. исходя из п.3, статический конструктор не должен иметь никаких модификаторов доступа — он вызывается автоматически при создании первого объекта или при первом обращении к статическому члену класса.

Например, в нашем классе можно определить вот такой статический конструктор:

class Building
{
    static int ordNumber;
    //статический конструктор
    static Building()
    {
        ordNumber = 0;
    }
}

Константы, поля и структуры для чтения

Последнее обновление: 03.10.2019

Полями класса называются обычные переменные уровня класса. Мы уже ранее рассматривали переменные — их объявление и инициализацию. Однако
некоторые моменты мы еще не затрагивали, например, константы и поля для чтения.

Константы

Константы характеризуются следующими признаками:

  • Константа должна быть проинициализирована при определении

  • После определения значение константы не может быть изменено

Константы предназначены для описания таких значений, которые не должны изменяться в программе. Для определения констант используется
ключевое слово const:

const double PI = 3.14;
const double E = 2.71;

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

class MathLib
{
    public const double PI=3.141;
    public const double E = 2.81;
	public const double K;		// Ошибка, константа не инициализирована
}

class Program
{
    static void Main(string[] args)
    {
        MathLib.E=3.8; // Ошибка, значение константы нельзя изменить
    }
}

Также обратите внимание на синтаксис обращения к константе. Так как неявно это статическое поле, для обращения к ней необходимо использовать
имя класса

class MathLib
{
    public const double PI=3.141;
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(MathLib.PI);
    }
}

Но следует учитывать, что мы не можем объявить константу с модификатором static. Но в этом собственно и нет смысла.

Константу можно определить как на уровне класса, так и внутри метода:

class MathLib
{
	public double GetCircleArea(double radius)
	{
		const double PI = 3.141;
		return PI * radius * radius;
	}
}

Поля для чтения

Поля для чтения можно инициализировать при их объявлении либо на уровне класса, либо инициилизировать и изменять в конструкторе. Инициализировать или изменять их значение в других местах нельзя, можно только считывать их значение.

Поле для чтения объявляется с ключевым словом readonly:

class MathLib
{
    public readonly double K = 23;	// можно так инициализировать

    public MathLib(double _k)
    {
        K = _k; // поле для чтения может быть инициализировано или изменено в конструкторе после компиляции
	}
	public void ChangeField()
    {
        // так нельзя
        //K = 34;
    }
}

class Program
{
    static void Main(string[] args)
    {
        MathLib mathLib = new MathLib(3.8);
        Console.WriteLine(mathLib.K); // 3.8

		//mathLib.K = 7.6; // поле для чтения нельзя установить вне своего класса
        Console.ReadLine();

    }
}

Сравнение констант

  • Константы должны быть определены во время компиляции, а поля для чтения могут быть определены во время выполнения программы.

    Соответственно инициализировать константу можно устанновить только при ее определении.

    Поле для чтения можно инициализировать либо при его определении, либо в конструкторе класса.

  • Константы не могут использовать модификатор static, так как уже неявно являются статическими. Поля для чтения могут быть как статическими, так и не статическими.

Структуры для чтения

Кроме полей для чтения в C# можно определять структуры для чтения. Для этого они предваряются модификатором readonly:

readonly struct User { }

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

readonly struct User
{
	public readonly string name;
	public User(string name)
	{
		this.name = name;
	}
}

То же самое касается и свойств, которые должны быть доступны только для чтения:

readonly struct User
{
	public readonly string Name { get; } // указывать readonly необязательно
	public int Age { get; } // свойство только для чтения
	public User(string name, int age)
	{
		this.Name = name;
		this.Age = age;
	}
}

НазадВперед

Static Class Objects

Static keyword works in the same way for class objects too. Objects declared static are allocated storage in static storage area, and have scope till the end of program.

Static objects are also initialized using constructors like other normal objects. Assignment to zero, on using static keyword is only for primitive datatypes, not for user defined datatypes.

constructor END destructor

You must be thinking, why was the destructor not called upon the end of the scope of condition, where the reference of object should get destroyed. This is because object was , which has scope till the program’s lifetime, hence destructor for this object was called when function exits.

Статические классы

Кроме того, что вы можете объявлять статические члены класса (поля, методы, свойства), в C# вы можете объявлять статическими целые классы. Объявить статический класс можно так:

static class SomeClass
 {

 }

при этом следует помнить, что если к классу применяется ключевое слово , все члены этого класса должны быть .  Где вы можете встретить статический класс C#? Одним из самых показательных примеров может быть тот самый класс , методами которого мы пользуемся с самого начала знакомства с языком C#.  Чтобы мы не считывали или не записывали в консоль — мы всегда используем статические методы статического класса (не создаем объект типа , а используем методы класса).

Чуть более формально

Итак, нужно создать шаблонный класс:

у которого:

  • внутри должен храниться или в зависимости от того, наследуется ли M от ;
  • методы-getter-ы должны возвращать либо , либо в зависимости от изменяемости/неизменяемости сообщения;
  • должен быть либо полный набор конструкторов и операторов копирования/перемещения, либо только конструктор и оператор перемещения;
  • метод должен либо возвращать копию хранимого intrusive_ptr, либо должен изымать значение intrusive_ptr и оставлять исходный в пустом состоянии. В первом случае должен быть константным, во втором — неконстантным методом.

Последние два пункта из перечня определяются параметром Ownership (а также мутабельностью сообщения, если для Ownership используется значение ).

Как это было решено

В данном разделе мы рассмотрим все составляющие, из которых получилось итоговое решение. Ну и само результирующее решение

Будут показаны максимально очищенные от всех отвлекающих внимание деталей фрагменты кода. Если кого-то интересует реальный код, то увидеть его можно здесь

Итоговый наследник message_holder_t

Теперь можно посмотреть на то, что же из себя представляет , для реализации которого потребовались все эти базовые классы и метафункции (из реализации удалена часть методов для конструирования экземпляра хранящегося в message_holder-е сообщения):

По сути все то, что мы разбирали выше, потребовалось для того, чтобы записать вот этот «вызов» двух метафункций:

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

А что было бы, если бы…

А вот если бы в C++ был настолько же мощен, как в D, то можно было бы написать что-то вроде:

Как по мне, так отличия слишком уж разительны. И они не в пользу текущего C++ 🙁
(разобранный выше C++ный код в виде одной сплошной «портянки» можно увидеть здесь).

Кстати говоря, я не очень сильно слежу за тем, что происходит в области предложений по метапрограммированию и рефлексии для будущих версий С++. Но из того, что помню, складывается ощущение, что предлагавшиеся Саттером метаклассы не очень упростят вот эту конкретную задачу. Как я понимаю, посредством метаклассов можно будет написать генератор классов . Может быть такой генератор получится и несложным в написании, но вряд ли такой подход в данном конкретном случае окажется выразительнее и понятнее, чем в случае действительно продвинутого .

Заключение

Как по мне, так этот пример показывает весь блеск и нищету C++. Да, можно сотворить все что угодно. В смысле, можно сделать шаблонный класс, содержимое которого будет кардинально меняться в зависимости от параметров шаблона.

Но вот чтобы сделать это придется несколько поломать мозги и написать на шаблонах столько вспомогательного кода, что копаться во всем этом не будет желания даже у автора.

Тем не менее, сам факт того, что на С++ можно такое сотворить, меня лично радует. Огорчает количество труда и объем кода, который для этого потребуется. Но, надеюсь, что со временем объем этого кода и его сложность будет только сокращаться. В принципе, это видно уже сейчас. Ибо для C++98/03 я даже не взялся бы такой трюк проделывать, тогда как начиная с C++11 делать подобное становится все проще и проще.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector