Petroglif

Code style

В этой главе определены правила оформления исходных кодов в проекте Papyrus.

Предопределенные комментарии

Для того, чтобы унифицировать формальные комментарии, мы сформулировали список наиболее употребительных и разместили его в заголовке файла pp.ini.

Общие правила

Заголовки файлов исходных кодов

Правила описания заголовка исходного файла базируются на простом принципе: меньше-лучше. Итак:

  • Первая строка - наименование файла прописными буквами без пути. Например SOMEMODULE.CPP.
  • Вторая строка - информация об авторстве и годы модификаций. Чаще всего это что-то вроде: Текст “Copyright (c)”, который встречается во многих файлах проекта - дань традиции. Можно написать так, а можно, например, Author. Но авторство важно.
  • Третья строка - определение кодировки файла с префиксом @codepage. Общее правило - файлы исходных кодов должны быть в кодировке UTF-8. Варианты возможны, но очень не желательны.
  • Четвертая строка - очень краткое (буквально в несколько слов) описание того, что в файле.
  • Пример:
    
    // C_ATURN.CPP
    // Copyright (c) A.Sobolev 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2005, 2006, 2007, 2016, 2019
    // @codepage UTF-8
    // Процедуры корректировки бухгалтерских проводок
    //
    

    Пожалуйста, не украшайте заголовок всякими безвкусными звездочками, длинными линиями из дефисов, знаков '=' и прочей ерунды - помните что чем файл короче тем проще с ним работать. И не надо вносить в исходные коды тексты лицензий и подробные описания ваших взглядов на жизнь.

    Макросы, предотвращающие повторное включение заголовочных файлов (include guard)

    Когда проект запускался директивы #pragma once не было. Потому до сих пор мы используем древнюю технику include guard с помощью макросов #ifndef-#define-#endif. Макроопределение формируется по следующему правилу: два подчеркивания, имя файла заглавными буквами, подчеркивание, символ H. Пример для файла slib.h:
    
    #ifndef __SLIB_H // {
    #define __SLIB_H
    // ... file-body
    #endif // } __SLIB_H
    

    Директивы препроцессора C/C++

    Отступы в директивах препроцессора должны предшествовать символу #, но не разделять этот символ с директивой.

    
    	// Правильные отступы
    	#if defined(A)
    		#define B
    	#else
    		#define C
    	#endif
    
    
    	// Неправильные отступы
    	#if defined(A)
    	#	define B
    	#else
    	#	define C
    	#endif
    

    Примеры кода

    Декларация класса

    
        //
        // Descr: Комментарий к классу (первая строка)
        //   продолжение комментария к классу на следующей строки с отступом в 2 пробела.
        //
        class Cls : public BaseCls {
        public:
        	Cls();
        	int    MemberFunc01(long * pID, const void * pData, size_t dataLen);
        	int    MemberFunc02(long id);
        	const void * MemberFunc03(long id, size_t * pDataLen) const;
        private:
        	virtual void VirtualMemberFunc04(void * pItem);
        
        	long   LastId;
        };
    

    Этот пример демонстрирует следующие особенности:

  • Наименования классов и структур начинается с прописной буквы
  • Наименования членов классов и структур так же начинаются с прописных букв
  • Открывающая скобка { для декларации находится на той же строке, что и наименование декларации
  • Ключевые слова public, private, protected для членов класса стартуют с той же позиции строки, что и ключевое слово class (struct, union, enum).
  • Определение функции (не ищите в ней смысл - это просто пример оформления текста)

    
        int Foo(int paramA, double paramB, const void * pParamC, char * pParamD, WierdObject & rObj)
        {
            int   ok = 1;
            int   local_var1 = 0;
            double local_var2 = 0.0; // простые типы крайне рекомендуется инициализировать при объявлении
            SomeObject * p_temp_obj = 0; // Указатель ОБЯЗАТЕЛЬНО инициализируется
            WierdObject & r_wobj = rObj;
            if(paramA > 0) {
                p_temp_obj = new SomeObject(pParamC);
                if(!p_temp_obj)
                    ok = 0;
            }
            if(ok) {
                if(pParamD) {
                    for(uint i = 0; i < 100; i++) {
                        local_var2 += 0.002;
                        pParamD[i] = (char)(local_var2 * 1.9);
                    }
                }
                while(local_var2 > 0.0) {
                    local_var2 -= 0.001;
                }
            }
            if(p_temp_obj && local_var1) do {
                p_temp_obj->Method(--local_var1);
            } while(local_var1 > 0);
            delete p_temp_obj; // Перед вызовами delete и free указатель никогда не проверяется на !0
            return ok;
        }
    
    Обратите внимание на следующие особенности:
  • Наименования аргументов функции: начинаются со строчной буквы, слова отделяются переходом от строчной к прописной букве. Аргументы-указатели всегда начинаются со строчной p (pParamC). Аргументы-ссылки всегда начинаются со строчной r (rObj).
  • Наименования локальных переменных: полностью состоят из строчных символов, слова разделяются подчеркиванием _ (local_var1). Локальные переменные указатели всегда начинаются с p_ (p_temp_obj). Локальные переменные-ссылки всегда начинаются с r_ (r_wobj).
  • В теле функции открывающие фигурные скобки всегда стоят в той же строке, что и оператор, требующий обрамления области его действия. Закрывающая фигурная скобка - всегда на отдельной строке.
  • Отступы

    Для отступов в тексте кодов применяется символ табуляции. Кроме как в начале строки символы табуляции не должны нигде более использоваться (разделение между токенами внутри строки - только пробелы).

    Декларации

    Настоятельно рекомендуется размещать каждую декларируемую переменную отдельным оператором.

    	
        int    a = 6; // Правильно
        int    b = 0; // Правильно
    	
        struct SomeStruc {
            int   A;   // Правильно 
            int * P_B; // Правильно
        };		
    

    	
        int    a = 6, b = 0; // Не желательно    struct SomeStruc {
            int   A, * P_B; // Не правильно
        };	
    

    Причиной такой рекомендации является слабая наглядность совмещенных в одном операторе декларируемых переменных. Кроме того, если типы переменных имеют квалификаторы указателя или ссылки, то совмещение в одном операторе декларации вносит дополнительную путаницу.Деклараций неинициализированных локальных переменных следует избегать. Любая неинициализированная переменная несет риск непредсказуемого поведения кода.

    Квалификатор const

    Если какая-то переменная, локальная или глобальная, равно как и член класса/структуры а также функция-член класса, может быть объявлена с квалификатором const, то это обязательно следует сделать. Несмотря на все его недостатки, const позволяет усилить надежность кода и облегчить его поддержку.

    Особое внимание следует уделить аргументам функций, передаваемых по ссылке либо по указателю. Если объект, на который ссылается указатель или ссылка не будет и, тем более, не должен меняться функцией, то аргумент следует декларировать с квалификатором const. Это важно для обеспечения неизменяемости объектов на большой глубине вызовов.Например:

    	
    	struct SomeBlock {
    		SomeBlock(int a, double b) : A(a), B(b)
    		{
    		}
    		int    A;
    		double B;
    	};
    	//
    	// 
    	// Не правильно: в этой функции аргумент pSb может быть объявлен как const
    	//
    	double Foo_Wrong(SomeBlock * pSb) 
    	{
    		return pSb ? (pSb->A * pSb->B) : 0.0;
    	}
    	//
    	// 
    	// Правильно: в этой функции аргумент pSb объявлен как const. 
    	// Теперь мы можем надеяться что после вызова функции ничего с 
    	// нашим объектом не случилось.
    	//
    	double Foo_Right(const SomeBlock * pSb) 
    	{
    		return pSb ? (pSb->A * pSb->B) : 0.0;
    	}
    	//
    	// 
    	// В этой функции мы инициализируем константный объект SomeBlock 
    	// и вызовем Foo_Right(const SomeBlock *)
    	//
    	double FooCaller()
    	{
    		const SomeBlock sb(20, 0.744);
    		// Компилятор не позволит нам вместо Foo_Right() вызвать Foo_Wrong()
    		// поскольку мы инициализировали sb как константный экземпляр SomeBlock.
    		return Foo_Right(&sb);
    	}
    

    Важное замечание относительно const

    Хотя я, как мог, обстоятельно рассмотрел необходимость использования модификатора const следует понимать, что во многих случаях это - декоративный элемент синтаксиса. Твердой гарантии неизменяемости объекта он не дает. Это связано во-первых, с возможность программиста преобразованием типов отменить const, а, во-вторых, что особенно печально, с отсутствием гарантии со стороны компилятора на соблюдение константности таких объектов.

    Операторы

    Оператор if

    	
            if(paramA > 0) {
                p_temp_obj = new SomeObject(pParamC);
            }
    

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

    	
            if(paramA > 0) {
                for(uint idx = 0; idx < count; idx++) {
                    ;
                }
            }
    

    Конструкция if else

    	
            if(paramA > 0) {
                p_temp_obj = new SomeObject(pParamC);
            }
            else {
                p_temp_obj = 0;
            }
    

    Конструкция if do while

    	
            if(c) do {
                foo(--c);
            } while(c);
    

    Тот случай, когда простая вложенная в if конструкция не обрамляется фигурными скобками, и, более того, тело if начинается на той же строке (это действительно исключение: никогда иначе не ставьте оператор, выполняемый в случае if на той же строке - вносит путаницу при чтении кода).

    	
            if(paramA > 0) p_temp_obj = new SomeObject(pParamC); // ТАК НЕЛЬЗЯ
    

    Конструкция switch case

    	
        switch(tag) {
            case tagconst1:
                SomethingToDo1();
                break;
            case tagconst2:
                SomethingToDo2();
                break;		
            case tagconst3:
                // @fallthrough
            case tagconst4:
                {
                    SomeClass some_class_instance;
                    some_class_instance.foo();
                }
                break;
            default:
                break;
        }
    

    В приведенном примере видны следующие особенности:

  • Открывающая фигурная скобка оператора switch() находится на той же строке (аналогично if, for, do, while).
  • Операторы case находятся на расстоянии одинарного отступа от switch.
  • Исполняемый код, относящийся к case отстоит от самого case на дистанции одинарного отступа.
  • break всегда находится на одинарном отступе от case
  • Если исполняемый код, относящийся к case требует заключения в фигурные скобки (по причине использования локальный по отношению к нему переменных), то этот код размещается еще на величину одного отступа от фигурных скобок, которые, в свою очередь, расположены на расстоянии единичного отступа от case. В соответствии с предыдущим правилом, общий break в этом случае размещен ниже закрывающей скобки и на том же уровне. Описанная позиция break важна для того, чтобы было видно, что он присутствует.
  • Если break в case отсутствует по умыслу программиста, то на его месте необходимо разместить предопределенный комментарий
    			
    			// @fallthrough
    		
  • Крайне нежелательно размещать оператор default где-либо кроме как в конце списка case'ов.
  • Специальные макросы

    В проекте часто используется целый ряд специальных макросов главным назначением которых является уменьшение кода и улучшение его читаемости. Большинство таких макросов определены в файле slib.h

    BIN булева унификация до целого 0 или 1

    Если необходимо привести результат выражения к одному из двух значений 0 или 1, то вместо:
    	
        int result = some_function_returning_int(argument) ? 1 : 0;      
    
    Следует писать:
    	
        int result = BIN(some_function_returning_int(argument));
    

    LOGIC булева унификация до целого false или true

    Этот макрос аналогичен предыдущему с той лишь разницей, что возвращает true или false:
    	
        bool result = some_function_returning_int(argument) ? true : false;
    
    Следует писать:
    	
        bool result = LOGIC(some_function_returning_int(argument));
    

    Не применяйте макросы BIN и LOGIC к числам с плавающей точкой! Точно известно, что в компиляторе Microsoft Visual C 7.3 это работает не правильно. Таким образом, гарантии, что это будет правильно работать в общем нет никакой.

    	
    	assert(BIN(0.0) == 0); // Может выдать ошибку
    

    MAX и MIN определение максимального и минимального, соответственно, значения из двух аргументов

    Это - очень популярные макросы в мире c/c++. Для проектов Papyrus они определены в slib.h и более нигде. Используйте только эти макросы, но не min, max или что-либо еще.

    Специальное исключение для библиотеки GDI+: перед включением gdiplus.h следует переопределить макросы min и max следующим образом:

    	 
    	#define max MAX
    	#define min MIN
    	#include 
    
    Иначе возникнут ошибки компиляции. В ближайшей перспективе мы намерены избавиться от зависимости от GDI+.

    SIZEOFARRAY определение размера статического массива

    Часто возникает необходимость получить размер статического массива. Каноническим подходом является следующий:
    	
           Entry array[591];
           for(size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
    	       do_somthing_with_entry(array[i]);
    	   }
    
    Так как писать каждый раз sizeof(array) / sizeof(array[0]) утомительно, то многие программисты применяют макросы для этой конструкции. В библиотеке SLIB используется макрос SIZEOFARRAY. Использование этого макроса в применении к примеру, приведенному выше, выглядит так:
    	
           Entry array[591];
           for(size_t i = 0; i < SIZEOFARRAY(array); i++) {
    	       do_somthing_with_entry(array[i]);
    	   }
    

    Будьте внимательны: попытка применить этот макрос к динамически распределяемому массиву или к указателю, о размере данных, на которые он ссылается, компилятор ничего не знает, приведет к фатальным последствиям.Например, в следующем коде применение макроса SIZEOFARRAY недопустимо.

    	
    	void foo(Entry * pArray, uint entryCount) 
    	{
    	   // Не делайте так! НИКОГДА!
           for(size_t i = 0; i < SIZEOFARRAY(pArray); i++) { // @badcode
    	       do_somthing_with_entry(pArray[i]);
    	   }
    	   // Компилятор не знает на массив какого размера ссылается pArray:
    	   // здесь надо писать так:
           for(size_t j = 0; j < entryCount; j++) { // @goodcode
    	       do_somthing_with_entry(pArray[j]);
    	   }	
       }
    

    Важным примером использования SIZEOFARRAY является вычисление массива unicode-символов (или же массива, элементы которого потенциально могут быть размером более одного байта). Пример:

    	
           TCHAR string_buf[591];
    	   // Здесь нелья писать sizeof(string_buf) поскольку в общем случае не известно
    	   // является длина элемента массива 1 байт или 2 байта 
    	   // (это зависит от предопределений сборки проекта).
           do_somthing_with_string(string_buf, SIZEOFARRAY(string_buf));
    

    oneof

    Вместо конструкции

    	
           if(a == 1 || a == 3) 
    
    следует писать
    	
    if(oneof2(a, 1, 3))
    
    аналогично
    	
           if(a == 1 || a == 3 || a == 8) 
    
    следует писать
    		
    	if(oneof3(a, 1, 3))
    
    В slib.h определены макросы от oneof2 до oneof14

    SETIFZ

    Вместо
    
           if(!a) 
              a = b; 
    
    следует писать:
    	
           SETIFZ(a, b);
    

    NZOR

    Вместо
    
           a = b ? b : c; 
    
    следует писать:
    	
           a = NZOR(b, c);
    

    SETMAX

    Вместо

    
           if(a < b)
              a = b;
    
    следует писать:
    	
           SETMAX(a, b); 
    

    SETMIN

    Вместо

    
           if(a > b)
              a = b;
    
    следует писать:
    	
           SETMIN(a, b); 
    

    SETFLAG установка/снятие битовых флагов

    Вместо

    
           if(a) 
              flags |= f;
           else
              flags &=  f;
    
    следует писать:
    	
           SETFLAG(flags, f, a);
    

    ASSIGN_PTR присваивание значения по указателю, если указатель не нулевой

    Вместо

    
           if(p)
              *p = a;
    
    следует писать:
    	
           ASSIGN_PTR(p, a);
    

    RVALUEPTR присваивает значению величину, на которую ссылается указатель, если последний не равен нулю

    Вместо

    
           if(p)
              a = *p;
    
    следует писать:
    	
           RVALUEPTR(a, p);
    
    Кроме того, макрос RVALUEPTR возвращает !0 если его второй аргумент (указатель) не равен нулю и ноль в противном случае. Этой особенностью можно пользоваться для альтернативной инициализации первого аргумента.

    Например:

    
          if(!RVALUEPTR(a, p)) 
              a = 0;
    

    MEMSZERO обнуляет память, занятую переменной

    Подробно описан ниже. См. стр. link.

    THISZERO обнуляет память, занятую переменной по указателю this

    Подробно описан ниже. См. стр. link.

    INITWINAPISTRUCT специальная инициализация структур Win API

    Макрос обнуляет память, выделенную под структуру Win API имеющую член cbSize и присваивает этому члену размер структуры.Например, конструкция:
    
    	SCROLLINFO si;
    	INITWINAPISTRUCT(si);
    

    То же самое, что и:

    
    	SCROLLINFO si;
    	MEMSZERO(si);
    	si.cbSize = sizeof(si);
    

    Задача этого макроса - максимально унифицировать код и сократить его размер.

    Простые преобразования типов указателей

    Следующие макросы осуществляют преобразования указателей к простым типам:

    PTR8(p)
    Преобразует указатель p к типу (uint8 *)
    PTR16(p)
    Преобразует указатель p к типу (uint16 *)
    PTR32(p)
    Преобразует указатель p к типу (uint32 *)
    PTR64(p)
    Преобразует указатель p к типу (uint64 *)
    PTRDBL(p)
    Преобразует указатель p к типу (double *)
    PTR8C(p)
    Преобразует указатель p к типу (const uint8 *)
    PTR16C(p)
    Преобразует указатель p к типу (const uint16 *)
    PTR32C(p)
    Преобразует указатель p к типу (const uint32 *)
    PTR64C(p)
    Преобразует указатель p к типу (const uint64 *)
    PTRDBLC(p)
    Преобразует указатель p к типу (const double *)
    PTRCHR(p)
    Преобразует указатель p к типу (char *) используя static_cast<>
    PTRCHRC(p)
    Преобразует указатель p к типу (const char *) используя static_cast<>
    PTRCHR_(p)
    Преобразует указатель p к типу (char *) используя reinterpret_cast<>
    PTRCHRC_(p)
    Преобразует указатель p к типу (const char *) используя reinterpret_cast<>

    Универсальные семантические правила

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

    Member function IsValid()

    Функция IsValid() проверяет корректность состояния экземпляра объекта.Спецификация функции:
    
    	int IsValid() const;
    

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

    Важно: метод IsValid() не должен быть предназначен только для проверки корректности кода объекта или консистентности внутреннего состояния экземпляра (хотя, кроме прочего, может и это делать). Это - не отладочная семантика. Метод IsValid() должен быть предназначен для регулярной продуктивной проверки состояния экземпляра объекта с целью выяснить можно ли его использовать.Примеры классов, содержащих такой метод: SFile, SBuffer (пример non-const метода), GoodsToObjAssoc.

    Member function IsConsistent()

    Функция IsConsistent() проверяет экземпляр объекта на предмет возможности хоть как-то его использовать. Другими словами, если эта функция возвращает 0, то никакие операции в экземпляром невозможны - он находится в разрушенном состоянии. В отличии от функции IsValid(), отрицательный результат которой лишь говорит о том, что в экземпляре что-то не так, но с ним еще можно что-то сделать (разрушить, инициализировать за-ново), отрицательный результат IsConsistent() вообще исключает возможность дальнейшего взаимодействия с экземпляром.

    Спецификация функции:

    
    	int IsConsistent() const;
    
    Чаще всего, но не всегда, IsConsistent() реализуется посредством проверки предварительно инициализированной сигнатуры экземпляра, значение которой стирается деструктором.

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

    Member function Z()

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

    Пример:

    
    	class ABC {
    	public:
    		ABC() : A(0), P_Buf(0), UsedBufSize(0), AllocatedBufSize(0)
    		{
    		}
    		 ABC()
    		{
    			// Деструктор, в отличии, от Z() освобождает память,
    			// выделенную под P_Buf, но не трогает "плоские" переменные,
    			// поскольку экземпляр объекта прекращает существование.
    			SAlloc::F(P_Buf);
    		}
    		ABC & Z()
    		{
    			// Мы вернули состояние переменной A в исходное состояние.
    			// Обнулили используемый размер буфера P_Buf, но 
    			// не стали освобождать память, выделенную под 
    			// этот буфер и менять AllocatedBufSize дабы
    			// при последующем использовании не распределять память вновь.
    			A = 0;
    			UsedBufSize = 0;
    			return *this;
    		}
    	private:
    		long   A;
    		void * P_Buf;
    		size_t UsedBufSize;
    		size_t AllocatedBufSize;
    	}
    

    Member function Destroy()

    Функция-член Destroy() должна полностью освободить все ресурсы, занятые экземпляром объекта кроме собственно самого экземпляра this. Вместе с тем, после вызова Destroy() все компоненты экземпляра должны быть валидными в том смысле, что любые дальнейшие операции с экземпляром допускаются. Например, если объект имеет внутренний указатель, распределением которого он управляет, то после освобождения внутреннего содержимого по этому указателю сам указатель должен быть обнулен.

    Спецификация функции:

    
    	void Destroy();
    
    Ключевое отличие Destroy() от Z() в том, что Z() имеет право не освобождать внутренние ресурсы, если считает, что для последующего использования экземпляра они пригодятся. У Destroy() такой опции нет.

    В общем случае должно выполняться правило: если класс имеет метод Destroy(), то деструктор класса может быть имплементирован единственным вызовом Destroy().Например:

    
    	class ABC {
    	public:
    		ABC()
    		{
    		}
    		 ABC()
    		{
    			Destroy();
    		}	
    		void Destroy();
    	};
    

    Member function IsEmpty()

    Функция-член IsEmpty возвращает ненулевое значение если экземпляр объекта является “пустым”. Термин “пустой” интерпретируется в зависимости от типа объекта. Спецификация функции, в общем случае, следующая:
    
    int    IsEmpty() const;
    

    Допускаются изменения указанной спецификации, однако, квалификатор const крайне важен. У вас должны быть очень существенные основания для того, чтобы спецификация такого метода была бы non-const.Если класс имеет методы Z() и IsEmpty(), то класс должен выполняться инвариант:

    
    	assert(instance.Z().IsEmpty());
    

    Member function IsEqual()

    Функция-член IsEqual() сравнивает экземпляр this некоторого объекта с экземпляром, переданным единственным аргументом функции. Чаще всего типом этого аргумента является тот же класс, которому принадлежит функция. Но это - не жесткое правило: могут быть и иные реализации IsEqual, принимающие в качестве аргумента какие-либо иные типы.

    Аргумент функции в общем случае следует передавать по const-ссылке.Функция возвращает значение типа int. Если this равен аргументу, то возвращается !0 (любое ненулевое значение: желательно единица, однако допустимы вариации). Если this не равен аргументу, то возвращает 0.

    Функция всегда имеет квалификатор const, равно как и аргумент, передаваемый ей так же имеет квалификатор const. То есть функция не меняет состояние this, равно как и состояние объекта, ссылка на который передана ей для сравнения. Вероятно, исключения из этого правила могут существовать, но в таком случае это необходимо объяснить в комментариях.Общее правило для формата вызова функций этого семейства FASTCALL. Правило не обязательное, но учитывая то, что функция имеет единственный аргумент, применение FASTCALL в этом случае весьма эффективно (большинство компиляторов, обрабатывающих FASTCALL, ничего на стеке передавать не будут - только в регистрах).

    Пример (продолжим примеры с выдуманным классом ABC):

    
    	class ABC {
    	public:
    		ABC() : A(0), P_Buf(0), UsedBufSize(0), AllocatedBufSize(0)
    		{
    		}
    		 ABC()
    		{
    			SAlloc::F(P_Buf);
    		}
    		int   FASTCALL IsEqual(const ABC & rS) const
    		{
    			int    eq = 1;
    			// Попутно проверим очевидные инварианты класса
    			assert(UsedBufSize <= AllocatedBufSize);
    			assert(rS.UsedBufSize <= rS.AllocatedBufSize);
    			assert(!UsedBufSize || P_Buf);
    			assert(!rS.UsedBufSize || rS.P_Buf);
    			if(A != rS.A)			
    			eq = 0;
    			else if(UsedBufSize != rS.UsedBufSize)
    			eq = 0;
    			else if(UsedBufSize && memcmp(P_Buf, rS.P_Buf, UsedBufSize) != 0) 
    			eq = 0;
    			return eq;
    		}
    		ABC & Z()
    		{
    			A = 0;
    			UsedBufSize = 0;
    			return *this;
    		}
    	private:
    		long   A;
    		void * P_Buf;
    		size_t UsedBufSize;
    		size_t AllocatedBufSize;
    	}
    

    Member function Copy()

    Метод Copy() реализует копирование экземпляра объекта, переданного аргументом функции в экземпляр this.

    Спецификация функции, в общем случае, следующая:

    
    	class ABC {
    	public:
    		int    FASTCALL Copy(const ABC & rS);
    	};
    
    Функция Copy() должна быть сконструирована так, чтобы реализовать полное (глубокое) копирование rS в *this. В общем случае, реализация copy-constructor и operator = класса, при необходимости, должны быть имплементированы простым вызовом метода Copy().

    Например:

    
    	class ABC {
    	public:
    		ABC()
    		{
    		}
    		ABC(const ABC & rS)
    		{
    			Copy(rS);
    		}
    		ABC & FASTCALL operator = (const ABC & rS)
    		{
    			Copy(rS);
    			return *this;
    		}
    		int    FASTCALL Copy(const ABC & rS);
    	};
    

    OOO "Петроглиф"
    Copyright © 2019