Static_assertstatic_assert
Содержание:
- Введение
- Статический членыStatic Members
- 1 Статические переменные
- Статический класс
- Когда использовать каждый из методов?
- Переменные
- Свойства
- Определение и инициализация статических переменных-членов класса
- ПримечанияRemarks
- Статические методы
- C++ не поддерживает статические конструкторы
- Преобразование типов данных в C#
- Статические члены классов
- Общая база для хранения указателя
Введение
В Java статическое ключевое слово в основном используется для управления памятью. Может использоваться с переменными, методами, блоками и вложенными классами. Это ключевое слово, которое используется для совместного использования одной и той же переменной или метода данного класса. В основном, static используется для постоянной переменной или метода, который одинаков для каждого экземпляра класса. Основной метод класса обычно помечается как статический.
Чтобы создать статический член (блок, переменную, метод, вложенный класс), вам необходимо предшествовать его объявлению с ключевым словом static. Когда член класса объявлен как статический, к нему можно получить доступ до того, как будут созданы объекты его класса, и без какой-либо ссылки на объект.
В языке программирования Java ключевое слово static является модификатором отсутствия доступа и может использоваться для следующих целей:
- Статический блок.
- переменная.
- метод.
- классы.
Статический членыStatic Members
Нестатический класс может содержать статические методы, поля, свойства или события.A non-static class can contain static methods, fields, properties, or events. Статический член вызывается для класса даже в том случае, если не создан экземпляр класса.The static member is callable on a class even when no instance of the class has been created. Доступ к статическому члены всегда выполняется по имени класса, а не экземпляра.The static member is always accessed by the class name, not the instance name. Существует только одна копия статического члена, независимо от того, сколько создано экземпляров класса.Only one copy of a static member exists, regardless of how many instances of the class are created. Статические методы и свойства не могут обращаться к нестатическим полям и событиям в их содержащем типе, и они не могут обращаться к переменной экземпляра объекта, если он не передается явно в параметре метода.Static methods and properties cannot access non-static fields and events in their containing type, and they cannot access an instance variable of any object unless it’s explicitly passed in a method parameter.
Более привычно объявление нестатического класса с несколькими статическими членами, чем объявление всего класса как статического.It is more typical to declare a non-static class with some static members, than to declare an entire class as static. Статические поля обычно используются для следующих двух целей: хранение счетчика числа созданных объектов или хранение значения, которое должно совместно использоваться всеми экземплярами.Two common uses of static fields are to keep a count of the number of objects that have been instantiated, or to store a value that must be shared among all instances.
Статические методы могут быть перегружены, но не переопределены, поскольку они относятся к классу, а не к экземпляру класса.Static methods can be overloaded but not overridden, because they belong to the class, and not to any instance of the class.
Несмотря на то, что поле не может быть объявлено как , поле const по своему поведению является статическим.Although a field cannot be declared as , a const field is essentially static in its behavior. Он относится к типу, а не к экземплярам типа.It belongs to the type, not to instances of the type. Поэтому к полям можно обращаться с использованием той же нотации , что и для статических полей.Therefore, fields can be accessed by using the same notation that’s used for static fields. Экземпляр объекта не требуется.No object instance is required.
C# не поддерживает статические локальные переменные (то есть переменные, объявленные в области действия метода).C# does not support static local variables (that is, variables that are declared in method scope).
Для объявления статических методов класса используется ключевое слово перед возвращаемым типом члена, как показано в следующем примере:You declare static class members by using the keyword before the return type of the member, as shown in the following example:
Статические члены инициализируются перед первым доступом к статическому члену и перед вызовом статического конструктора, если таковой имеется.Static members are initialized before the static member is accessed for the first time and before the static constructor, if there is one, is called. Для доступа к члену статического класса следует использовать имя класса, а не имя переменной, указывая расположение члена, как показано в следующем примере:To access a static class member, use the name of the class instead of a variable name to specify the location of the member, as shown in the following example:
Если класс содержит статические поля, должен быть указан статический конструктор, который инициализирует эти поля при загрузке класса.If your class contains static fields, provide a static constructor that initializes them when the class is loaded.
Вызов статического метода генерирует инструкцию вызова в промежуточном языке Microsoft (MSIL), в то время как вызов метода экземпляра генерирует инструкцию , которая также проверяет наличие ссылок на пустые объекты.A call to a static method generates a call instruction in Microsoft intermediate language (MSIL), whereas a call to an instance method generates a instruction, which also checks for null object references. Однако в большинстве случаев разница в производительности двух видов вызовов несущественна.However, most of the time the performance difference between the two is not significant.
1 Статические переменные
Когда класс загружается в память, для него сразу создается статический объект класса. Этот объект хранит статические переменные класса (статические поля класса). Статический объект класса существует, даже если не был создан ни один обычный объект класса.
Когда мы описываем переменные в классе, мы указываем, будут ли эти переменные созданы всего один раз или же нужно создавать их копии для каждого объекта. По умолчанию создаётся новая копия переменной для каждого объекта.
Статическая (static) же переменная привязана к статическому объекту класса и всегда существует в единственном экземпляре.
Чтобы создать статическую переменную класса, нужно перед ее именем написать ключевое слово . Общий вид объявления статической переменной такой:
Если статической переменной не присвоить стартовое значение, она инициализируется значением по умолчанию:
| Тип | Значение по умолчанию |
|---|---|
| (то же самое, что и ) | |
| и любые классы |
Примеры:
| Код | Примечание |
|---|---|
Обращаться к статической переменной в ее классе можно просто по имени. Если обращение идет из другого класса, то перед именем статической переменной нужно писать имя класса.
Пример:
| Переменная | Класс | Обращение к переменной вне класса |
|---|---|---|
| Переменная , вне класса не видна | ||
| Переменная , вне класса не видна | ||
| Переменная , вне класса не видна |
Статический класс
Класс можно сделать статическим, только если он является вложенным классом. Вложенный статический класс не нуждается в ссылке на Outer. В этом случае статический класс не может получить доступ к нестатическим членам класса Outer. Давайте рассмотрим пример, чтобы понять, как это работает
public class NestedExample{
private static String str= "Edureka"
//Static class
static class MyNestedClass{
//non-static method
public void disp(){
System.out.println(str);
}
}
public static void main(String args[]){
NestedExample.MyNestedClass obj = new NestedExample.MyNestedClass();
obj.disp();
}
Когда вы выполняете приведенный выше код, ваш вывод выглядит так:
Когда использовать каждый из методов?
Выбор того, какой из методов использовать, может показаться достаточно сложным. Тем не менее с опытом этот выбор делать гораздо проще.
Чаще всего метод класса используется тогда, когда нужен генерирующий метод, возвращающий объект класса. Как видим, метод класса используется для создания объекта класса по году рождения, а не возрасту.
Статические методы в основном используются как вспомогательные функции и работают с данными, которые им передаются.
Запомнить
- Методы экземпляра класса получают доступ к объекту класса через параметр и к классу через .
- Методы класса не могут получить доступ к определённому объекту класса, но имеют доступ к самому классу через .
- Статические методы работают как обычные функции, но принадлежат области имён класса. Они не имеют доступа ни к самому классу, ни к его экземплярам.
- Даже если вы программируете только ради интереса, изучение ООП в Python поможет писать код так, чтобы в дальнейшем было легче искать ошибки и использовать его повторно.
- Давайте синхронизировать потоки в Python
- Овладей Python, создавая реальные приложения. Часть 1
- Полезные хитрости на Python от А до Я
- Изучение Python: от нуля до мастера
Перевод статьи Rachit TayalPython’s Instance, Class and Static Methods
Переменные
В контексте статической функции переменные сохраняют свое значение даже после выхода программы из области видимости содержащей их функции. В контексте метода класса статические переменные обладают дополнительным поведением, подобным статическим свойствам: изменения их значений отражаются во всех экземплярах класса. Несмотря на схожесть со статическими свойствами, статические переменные доступны только в теле функции или метода.
Статические переменные часто используются в технике оптимизации под названием мемоизация. Ее целью является ускорение дорогостоящей операции за счет кэширования результатов и сохранения их для последующего вызова с теми же параметрами.
В данном примере мы создаем уникальный для предоставленных параметров хеш и используем его для уникальной идентификации вызова в качестве ключа. Если значение в качестве индекса для не найдено, то выполняем и сохраняем его вывод в индексе , принадлежащему . Каждый последующий вызов для с теми же параметрами будет обходить вызов и возвращать значение из предыдущего вызова:
Свойства
В отличие от обычных свойств, изменение значения статического свойства во время выполнения программы повлияет на все экземпляры содержащего свойство класса. Даже на те, для которых еще не созданы экземпляры. Таким образом, статические свойства можно рассматривать в качестве «констант изменяемого класса». На статические свойства можно ссылаться только с помощью оператора разрешения области видимости.
Благодаря природе статических свойств их можно использовать для реализации шаблона одиночка (Singleton). Одиночка содержит один и тот же экземпляр класса на протяжении всего выполнения программы.
В данном примере первый вызов создает и назначает экземпляр для и возвращает его. Каждый последующий вызов будет возвращать один и тот же экземпляр , ранее присвоенный :
Определение и инициализация статических переменных-членов класса
Когда мы объявляем статическую переменную-член внутри тела класса, то мы сообщаем компилятору о существовании статической переменной-члене, но не о её определении (аналогией является предварительное объявление). Поскольку статические переменные-члены не являются частью отдельных объектов класса (они обрабатываются аналогично глобальным переменным и инициализируются при запуске программы), то вы должны явно определить статический член вне тела класса — в глобальной области видимости.
В программе, приведенной выше, это делается следующей строчкой:
int Anything::s_value = 3; // определяем статическую переменную-член
| 1 | intAnything::s_value=3;// определяем статическую переменную-член |
Здесь мы определили статическую переменную-член класса и инициализировали её значением . Если же инициализатор не предоставлен, то C++ инициализирует значением .
Обратите внимание, это определение статического члена не подпадает под действия спецификаторов доступа: вы можете определить и инициализировать , даже если он будет private (или protected). Если класс определен в заголовочном файле, то определение статического члена обычно помещается в файл с кодом класса (например, в Anything.cpp)
Если класс определен в файле .cpp, то определение статического члена обычно пишется непосредственно под классом. Не пишите определение статического члена класса в заголовочном файле (подобно глобальным переменным). Если этот заголовочный файл подключают больше одного раза, то вы получите несколько определений одного члена, что приведет к ошибке компиляции
Если класс определен в заголовочном файле, то определение статического члена обычно помещается в файл с кодом класса (например, в Anything.cpp). Если класс определен в файле .cpp, то определение статического члена обычно пишется непосредственно под классом. Не пишите определение статического члена класса в заголовочном файле (подобно глобальным переменным). Если этот заголовочный файл подключают больше одного раза, то вы получите несколько определений одного члена, что приведет к ошибке компиляции.
ПримечанияRemarks
Обычно при вызове статического члена необходимо указать имя типа и имя нужного члена.Ordinarily, when you call a static member, you provide the type name along with the member name. Повторный ввод одного и того же имени типа для вызова относящихся к нему элементов может сделать код слишком длинным и сложным.Repeatedly entering the same type name to invoke members of the type can result in verbose, obscure code. Например, следующее определение класса ссылается на ряд членов класса Math.For example, the following definition of a class references a number of members of the Math class.
Поскольку явно ссылаться на класс Math при каждой ссылке на член не требуется, директива создает гораздо более понятный код:By eliminating the need to explicitly reference the Math class each time a member is referenced, the directive produces much cleaner code:
импортирует только доступные статические члены и вложенные типы, объявленные в указанном типе. imports only accessible static members and nested types declared in the specified type. Унаследованные члены не импортируются.Inherited members are not imported. Можно импортировать из любого именованного типа с помощью директивы using static, включая модули Visual Basic.You can import from any named type with a using static directive, including Visual Basic modules. Если функции F# верхнего уровня отображаются в метаданных как статические члены именованного типа, имя которого является допустимым идентификатором C#, то эти функции F# можно импортировать.If F# top-level functions appear in metadata as static members of a named type whose name is a valid C# identifier, then the F# functions can be imported.
делает методы расширения, объявленные в указанном типе, доступными для поиска метода расширения. makes extension methods declared in the specified type available for extension method lookup. Тем не менее имена методов расширения не импортируются в область для неквалифицированной ссылки в коде.However, the names of the extension methods are not imported into scope for unqualified reference in code.
Методы с тем же именем, импортированные из различных типов разными директивами в том же блоке компиляции или пространстве имен, формируют группу методов.Methods with the same name imported from different types by different directives in the same compilation unit or namespace form a method group. Разрешение перегрузки в этих группах методов соответствует обычным правилам языка C#.Overload resolution within these method groups follows normal C# rules.
Статические методы
Статические методы декларируются при помощи декоратора . Им не нужен определённый первый аргумент (ни , ни ).
Их можно воспринимать как методы, которые “не знают, к какому классу относятся”.
Таким образом, статические методы прикреплены к классу лишь для удобства и не могут менять состояние ни класса, ни его экземпляра.
С теорией достаточно. Давайте разберёмся с работой методов, создав объект нашего класса и вызвав поочерёдно каждый из методов: instancemethod, classmethod and staticmethod.
Пример выше подтверждает то, что метод instancemethod имеет доступ к объекту класса ToyClass через аргумент . Кстати, вызов функции используется лишь для удобства, то есть можно использовать и .
Теперь давайте вызовем метод класса:
Мы видим, что метод класса classmethod() имеет доступ к самому классу ToyClass, но не к его конкретному экземпляру объекта. Запомните, в Python всё является объектом. Класс тоже объект, который мы можем передать функции в качестве аргумента.
Заметьте, что и — не обязательные названия и эти параметры можно называть иначе.
Это лишь общепринятые обозначения, которым следуют все. Тем не менее они должны находиться первыми в списке параметров.
Вызовем статический метод:
Да, это может вас удивить, но статические методы можно вызывать через объект класса. Вызов через точку нужен лишь для удобства. На самом же деле в случае статического метода никакие аргументы ( или) методу не передаются.
То есть статические методы не могут получить доступ к параметрам класса или объекта. Они работают только с теми данными, которые им передаются в качестве аргументов.
Теперь давайте вызовем те же самые методы, но на самом классе.
Метод класса и статический метод работают, как нужно. Однако вызов метода экземпляра класса выдаёт TypeError, так как метод не может получить на вход экземпляр класса.
Теперь, когда вы знаете разницу между тремя видами методов, давайте рассмотрим реальный пример для понимания того, когда и какой метод стоит использовать. Пример взят отсюда.
C++ не поддерживает статические конструкторы
Если вы можете инициализировать обычную переменную-член через конструктор, то по логике вещей вы должны иметь возможность инициализировать статические переменные-члены через статический конструктор. И, хотя некоторые современные языки действительно поддерживают статические конструкторы именно для этой цели, язык C++, к сожалению, не является одним из таковых.
Если ваша статическая переменная может быть инициализирована напрямую, то конструктор не нужен: вы можете определить статическую переменную-член, даже если она является private. Мы делали это в вышеприведенном примере с . Вот еще один пример:
class Something
{
public:
static std::vector<char> s_mychars;
};
std::vector<char> Something::s_mychars = { ‘o’, ‘a’, ‘u’, ‘i’, ‘e’ }; // определяем статическую переменную-член
|
1 |
classSomething { public staticstd::vector<char>s_mychars; }; std::vector<char>Something::s_mychars={‘o’,’a’,’u’,’i’,’e’};// определяем статическую переменную-член |
Если для инициализации вашей статической переменной-члена требуется выполнить код (например, цикл), то есть несколько разных способов это сделать. Следующий способ является лучшим из них:
#include <iostream>
#include <vector>
class Something
{
private:
static std::vector<char> s_mychars;
public:
class _nested // определяем вложенный класс с именем _nested
{
public:
_nested() // конструктор _nested() инициализирует нашу статическую переменную-член
{
s_mychars.push_back(‘o’);
s_mychars.push_back(‘a’);
s_mychars.push_back(‘u’);
s_mychars.push_back(‘i’);
s_mychars.push_back(‘e’);
}
};
// Статический метод для вывода s_mychars
static void getSomething() {
for (auto const &element : s_mychars)
std::cout << element << ‘ ‘;
}
private:
static _nested s_initializer; // используем статический объект класса _nested для гарантии того, что конструктор _nested() выполнится
};
std::vector<char> Something::s_mychars; // определяем нашу статическую переменную-член
Something::_nested Something::s_initializer; // определяем наш статический s_initializer, который вызовет конструктор _nested() для инициализации s_mychars
int main() {
Something::getSomething();
return 0;
}
|
1 |
#include <iostream> classSomething { private staticstd::vector<char>s_mychars; public class_nested// определяем вложенный класс с именем _nested { public _nested()// конструктор _nested() инициализирует нашу статическую переменную-член { s_mychars.push_back(‘o’); s_mychars.push_back(‘a’); s_mychars.push_back(‘u’); s_mychars.push_back(‘i’); s_mychars.push_back(‘e’); } }; // Статический метод для вывода s_mychars staticvoidgetSomething(){ for(auto const&element s_mychars) std::cout<<element<<‘ ‘; } private static_nested s_initializer;// используем статический объект класса _nested для гарантии того, что конструктор _nested() выполнится }; std::vector<char>Something::s_mychars;// определяем нашу статическую переменную-член Something::_nested Something::s_initializer;// определяем наш статический s_initializer, который вызовет конструктор _nested() для инициализации s_mychars intmain(){ Something::getSomething(); return; } |
Результат выполнения программы:
При определении статического члена вызовется конструктор по умолчанию _nested() (так как является объектом класса _nested). Мы можем использовать этот конструктор для инициализации любых статических переменных-членов класса Something. Самое крутое здесь — это то, что весь код инициализации скрыт внутри исходного класса со статическим членом.
Преобразование типов данных в C#
Преобразование типов данных, это приведение одного типа к другому. Например, приведение целочисленной переменной к числу с плавающей точной, или преобразование числа в строку. В C# выделяют два варианта преобразования типов:
- Неявное преобразование типов. Это, так называемое безопасное преобразование типов в C#. Например, преобразование из типа float (более «маленький» тип) в тип double (более «большой» тип). При таком преобразовании никакая информация не «потеряется», что при обратном преобразовании вполне возможно.
- Явное преобразование типов. Такое преобразование выполняется программистом с прямым указанием типа, к которому нужно привести переменную. Для такого преобразования требуется наличие оператора преобразования.
А теперь, я покажу как можно использовать явное преобразование типов в C#:
using System;
namespace TypesConvertion
{
class Program
{
static void Main(string[] args)
{
//Число с плавающей точкой
double doubleData = 245.45;
//Вывод этого числа
Console.WriteLine(doubleData);
//А теперь, мы сделаем на основе этого числа, цело число
int intData = (int)doubleData;
//Вывод целого числа
Console.WriteLine(intData);
//Чтобы окно не закрылось раньше времени
Console.ReadKey();
}
}
}
Преобразование типа double в тип int выполнялось в выделенной строке (строке с номером 16). Если вы соберете данный пример и запустите его, то увидите, что преобразование типов прошло, т.е. из числа с плавающей точкой мы получили целое число.
Так же, для преобразования данных из одного типа в другой, есть целый класс с именем Convert, у которого имеются специальные методы. Ниже, в таблице, представлены основные методы этого класса.
| Название метода | Целевой тип |
|---|---|
| ToBoolean | bool |
| ToByte | byte |
| ToChar | char |
| ToDouble | double |
| ToSingle | float |
| ToInt32 | int |
| ToInt64 | long |
| ToString | string |
А теперь, пример использования класса Convert и его методов:
using System;
namespace TypesConvertion
{
class Program
{
static void Main(string[] args)
{
//Число с плавающей точкой
double doubleData = 245.45;
//Вывод этого числа
Console.WriteLine(doubleData);
//А теперь, мы сделаем на основе этого числа, цело число
int intData = Convert.ToInt32(doubleData);
//Вывод целого числа
Console.WriteLine(intData);
//Чтобы окно не закрылось раньше времени
Console.ReadKey();
}
}
}
Как видите, это практически тот же код, что был в первом примере, но с использованием другого подхода в преобразовании типов.
Пользоваться возможность преобразования типов вам придется довольно часто!
Статические члены классов
Последнее обновление: 14.05.2020
Кроме обычных методов и полей класс может иметь статические поля, методы, константы. Значения таких полей, методов и констант относятся в целом ко всему классу, а
не к отдельным объектам. Для определения статических переменных, констант и методов перед их объявлением указывается ключевое слово static.
Статические поля
Статические поля хранят состояние всего класса. Статическое поле определяется как и обычное, только впереди ставится ключевое слово static. Например, рассмотрим класс Employee, который представляет работника:
void main (){
Employee bob = Employee("Bob", 55);
bob.checkAge();
Employee.retirenmentAge = 65;
bob.checkAge();
}
class Employee{
String name;
int age;
static int retirenmentAge = 60;
Employee(this.name, this.age);
checkAge(){
if(age >= retirenmentAge){
print("Пора на пенсию");
}
else{
print("До пенсии еще ${retirenmentAge - age} лет");
}
}
}
В классе работника определены поля для хранения имени и возраста — у каждого отдельного работника может быть свое имя и свой возраст. Кроме того,
определено статическое поле для хранения пенсионного возраста retirenmentAge. Оно относится не к конкретному работнику, а ко всем работникам, то есть ко всему классу Employee в целом, так как нельзя для каждого отдельного работника
установить свой пенсионный групп. Затем в самом классе мы можем использовать это поле как и любые другие.
Если мы хотим обратиться к этому полю вне класса, например, изменить его значение, то для обращения применяется имя класса:
Employee.retirenmentAge = 65;
Статические константы
Если пенсионный возраст менять не планируется, то мы можем определить как статическую константу:
void main (){
Employee bob = Employee("Bob", 55);
bob.checkAge();
print("Retirenment age: ${Employee.retirenmentAge}");
}
class Employee{
String name;
int age;
static const int retirenmentAge = 65;
Employee(this.name, this.age);
checkAge(){
if(age >= retirenmentAge){
print("Пора на пенсию");
}
else{
print("До пенсии еще ${retirenmentAge - age} лет");
}
}
}
Для определения статической константы можно использовать либо выражение , либо . То есть следующие случаи будут эквивалентны:
static const int retirenmentAge = 65; static final int retirenmentAge = 65;
Статические методы
Статические методы также относятся ко всему классу и предваряются ключевым словом . Как правило, статические методы выполняют такие вычисления,
которые не затрагивают состояние или поведение объекта:
void main (){
int a = Operation.sum(3, 5);
int b = Operation.subtract(15, 7);
int c = Operation.multiply(3, 5);
print("a=$a b=$b c=$c"); // a=8 b=8 c=15
}
class Operation{
static int sum(int x, int y) => x + y;
static int subtract(int x, int y) => x - y;
static int multiply(int x, int y) => x * y;
}
В данном случае для методов sum, subtract, multiply не имеет значения, какой именно экземпляр класса Operation используется. Эти методы работают только с параметрами, не затрагивая состояние класса. Поэтому их можно определить как статические.
Статические методы могут использоваться для управления статическими полями, то есть состоянием, которое относится ко всему классу. В то же время в статических методах мы не можем использовать
нестатические методы и поля класса:
void main (){
Employee bob = Employee("Bob", 55);
Employee.setRetirenmentAge(63);
print(Employee.retirenmentAge);
}
class Employee{
String name;
int age;
static int retirenmentAge = 65;
Employee(this.name, this.age);
checkAge(){
if(age >= retirenmentAge){
print("Пора на пенсию");
}
else{
print("До пенсии еще ${retirenmentAge - age} лет");
}
}
static void setRetirenmentAge(int value){
if(value > 50 && value < 70){
retirenmentAge = value;
}
else{
print("Некорректное значение");
}
}
}
В данном случае статический метод setRetirenmentAge позволяет управлять статической переменной retirenmentAge.
В чем преимущества статических полей и методов перед нестатическими? Статические поля и методы потребляют меньше памяти, чем нестатические. Нестатическая переменная после инициализации (при первом присвоении
значения) сразу начинает потреблять память независимо от того, используется она или нет.
А статические поля и методы не инициализируются до того момента, пока они не начнут использоваться в программе.
Соответственно потреблять память они начинают только тогда, когда непосредственно начинают использоваться.
НазадВперед
Общая база для хранения указателя
Начнем с общего базового типа, который хранит соответствующий intrusive_ptr, а также предоставляет общий набор методов, которые нужны любой из реализаций :
У этого шаблонного класса два параметра. Первый, Payload, задает тип, который должны использовать методы-getter-ы. Тогда как второй, Envelope, задает тип для intrusive_ptr. В случае, когда тип сообщения наследуется от оба эти параметра будут иметь одинаковое значение. А вот если сообщение не наследуется от , тогда в качестве Payload будет тип сообщения, а в качестве Envelope будет выступать .
Думаю, что в основном содержимое этого класса не вызывает вопросов
Но отдельно следует обратить внимание на две вещи
Во-первых, сам указатель, т.е. атрибут m_msg, определен в protected секции для того, чтобы классы наследники имели к нему доступ.
Во-вторых, для этого класса сам компилятор генерирует все необходимые конструкторы и операторы копирования/перемещения. И на уровне этого класса мы пока ничего не запрещаем.

