![]()  | 
| Главная > Программирование > Языки C/C++/Builder > | 
Краткий FAQ по C++ | 
Секция 2 из 3 - Предыдущая - Следующая
Все секции
- 1
- 2
- 3
[9.4] Что сделать, чтобы определить функцию - не член класса как встроенную?
Когда вы объявляете встроенную функцию, это выглядит как обычное объявление функции:
void f(int i, char c);
Но перед определением встроенной функции пишется слово inline, и само определение помещается в заголовочный файл:
	inline
	void f(int i, char c)
	{
		// ...
	}
Примечание: Необходимо, чтобы определение встроенной функции (часть между {...}) была помещена в заголовочный файл, за исключением того случая, когда функция используется только в одном .cpp файле. Если вы помещаете определение встроенной функции в .cpp файл, а вызываете ее из другого .cpp файла, то вы получаете ошибку "unresolved external" ("ненайденный внешний объект") от компоновщика (linker).
(Примечание переводчика: На всякий случай уточню, что само помещение определения функции в заголовочный файл НЕ делает ее встроенной. Это требуется только для того, чтобы тело функции было видно во всех местах, где она вызывается. Иначе невозможно обеспечить встраивание функции. - YM)
[9.5] Как сделать встроенной функцию - член класса?
Когда вы объявляете встроенную функцию - член класса, это выглядит как обычное объявление функции - члена:
	class Fred {
	public:
		void f(int i, char c);
	};
Но когда перед определением встроенной функции пишется слово inline, а само определение помещается в заголовочный файл:
	inline
	void Fred::f(int i, char c)
	{
		// ...
	}
Примечание: Необходимо, чтобы определение встроенной функции (часть между {...}) была помещена в заголовочный файл, за исключением того случая, когда функция используется только в одном .cpp файле. Если вы помещаете определение встроенной функции в .cpp файл, а вызываете ее из другого .cpp файла, то вы получаете ошибку "unresolved external" ("ненайденный внешний объект") от компоновщика (linker).
[9.6] Есть ли другой способ определить встроенную функцию - член класса?
Да, определите функцию-член класса в теле самого класса:
    class Fred {
    public:
        void f(int i, char c)
        {
             // ...
        }
    };
Хотя такой вид определения проще для создателя класса, но он вызывает определенные трудности для пользователя, поскольку здесь смешивается, что делает класс и как он это делает. Из-за этого неудобства предпочтительно определять функции-члены класса вне тела класса, используя слово inline [9.5]. Причина такого предпочтения проста: как правило, множество людей используют созданный вами класс, но только один человек пишет его (вы); предпочтительно делать вещи, облегчающие жизнь многим
[9.7] Обязательно ли встроенные функции приведут к увеличению производительности?
Нет.
Слишком большое количество встроенных функций может привести к увеличению размера кода, что в свою очередь может оказать негативное влияние на скорость в системах со страничной организацией памяти.
РАЗДЕЛ [10]: Конструкторы
[10.1] Что такое конструкторы?
Конструкторы делают объекты из ничего.
Конструкторы похожи на инициализирующие функции. Они превращают свалку случайных бит в работающий объект. В минимальном случае, они инициализируют используемые переменные класса. Также они могут выделять ресурсы (память, файлы, флажки, сокеты и т. п.).
"ctor" - часто используемое сокращение для слова конструктор.
[10.2] Есть ли разница между объявлениями List x; и List x();?
Огромная!
Предположим, что List - это имя класса. Тогда функция f() объявляет локальный объект типа List с именем x:
    void f()
    {
      List x;     // Локальный объект с именем x (класса List)
      // ...
    }
Но функция g() объявляет функцию x(), которая возвращает объект типа List:
    void g()
    {
      List x();   // Функция с именем x (возвращающая List)
      // ...
    }
[10.3] Как из одного конструктора вызвать другой конструктор для инициализации этого объекта?
(Имеются в виду несколько перегруженных конструкторов для одного объекта - примечание переводчика.)
Никак.
Проблема вот в чем: если вы вызовете другой конструктор, компьютер создаст и проинициализирует временный объект, а не объект, из которого вызван конструктор. Вы можете совместить два конструктора, используя значения параметров по умолчанию, или вы можете разместить общий для двух конструкторов код в закрытой (private) функции - члене init().
[10.4] Всегда ли конструктор по умолчанию для Fred выглядит как Fred::Fred()?
Нет. Конструктор по умолчанию - это конструктор, который можно вызывать без аргументов. Таким образом, конструктор без аргументов безусловно является конструктором по умолчанию:
    class Fred {
    public:
      Fred();   // Конструктор по умолчанию: может вызываться без аргументов
      // ...
    };
Однако возможно (и даже вероятно), что конструктор по умолчанию может принимать аргументы, при условии что для всех них заданы значения по умолчанию:
    class Fred {
    public:
      Fred(int i=3, int j=5);   // Конструктор по умолчанию: может вызываться без аргументов
      // ...
    };
[10.5] Какой конструктор будет вызван, если я создаю массив объектов типа Fred?
Конструктор по умолчанию [10.4] для класса Fred (за исключением случая, описанного ниже)
Не существует способа заставить компилятор вызвать другой конструктор (за исключением способа, описанного ниже). Если у вашего класса Fred нет конструктора по умолчанию [10.4], то при попытке создания массива объектов типа Fred вы получите ошибку при компиляции.
    class Fred {
    public:
      Fred(int i, int j);
      // ... предположим, что для класса Fred нет конструктора по умолчанию [10.4]...
    };
    int main()
    {
      Fred a[10];               // ОШИБКА: У Fred нет конструктора по умолчанию
      Fred* p = new Fred[10];   // ОШИБКА: У Fred нет конструктора по умолчанию
    }
Однако если вы создаете, пользуясь STL [32.1], vector<Fred> вместо простого массива (что вам скорее всего и следует делать, поскольку массивы опасны [21.5]), вам не нужно иметь конструктор по умолчанию в классе Fred, поскольку вы можете задать объект типа Fred для инициализации элементов вектора:
    #include <vector>
    using namespace std;
    int main()
    {
      vector<Fred> a(10, Fred(5,7));
      // Десять объектов типа Fred
      // будут инициализированы Fred(5,7).
      // ...
    }
Хотя вам следует пользоваться векторами, а не массивами, иногда бывают ситуации, когда необходим именно массив. Специально для таких случаев существует способ записи явной инициализации массивов. Вот как это выглядит:
    class Fred {
    public:
      Fred(int i, int j);
    // ... предположим, что для класса Fred
    // нет конструктора по умолчанию [10.4]...
    };
    int main()
    {
      Fred a[10] = {
        Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7),
        Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7)
      };
      // Десять объектов массива Fred
      // будут инициализированы Fred(5,7).
      // ...
    }
Конечно, вам не обязательно использовать Fred(5,7) для каждого элемента. Вы можете использовать любые числа или даже параметры и другие переменные. Суть в том, что такая запись (a) возможна, но (б) не так хороша, как запись для вектора. Помните: массивы опасны [21.5]. Если у вы не вынуждены использовать массивы - используйте вектора.
[10.6] Должны ли мои конструкторы использовать "списки инициализации" или "присваивания значений"?
Конструкторы должны инициализировать все члены в списках инициализации.
Например, пусть конструктор инициализирует член x_, используя список инициализации: Fred::Fred() : x_(какое-то-выражение) { }. С точки зрения производительности важно заметить, что какое-то-выражение не приводит к созданию отдельного объекта для копирования его в x_: если типы совпадают, то какое-то-выражение будет создано прямо в x_.
Напротив, следующий конструктор использует присваивание: Fred::Fred() { x_ = какое-то-выражение; }. В этом случае какое-то-выражение приводит к созданию отдельного временного объекта, который потом передается в качестве параметра оператору присваивания объекта x_, а потом уничтожается при достижении точки с запятой. Это неэффективно.
Есть и еще один источник неэффективности: во втором случае (с присваиванием) конструктор по умолчанию для объекта (неявно вызванный до { тела конструктора) мог, например, выделить по умолчанию некоторое количество памяти или открыть файл. Вся эта работа окажется проделанной впустую, если какое-то-выражение и/или оператор присваивания привели к закрытию этого файла и/или освобождению памяти (например, если конструктор по умолчанию выделил недостаточно памяти или открыл не тот файл).
Выводы: при прочих равных условиях ваш код будет более быстрым, если вы используете списки инициализации, а не операторы присваивания.
[10.7] Можно ли пользоваться указателем this в конструкторе?
Некоторые люди не рекомендуют использовать указатель this в конструкторе, потому что объект, на который указывает this еще не полностью создан. Тем не менее, при известной осторожности, вы можете использовать this в конструкторе (в {теле} и даже в списке инициализации [10.6]).
Как только вы попали в {тело} конструктора, легко себе вообразить, что можно использовать указатель this, поскольку все базовые классы и все члены уже полностью созданы. Однако даже здесь нужно быть осторожным. Например, если вы вызываете виртуальную функцию (или какую-нибудь функцию, которая в свою очередь вызывает виртуальную функцию) для этого объекта, мы можете получить не совсем то, что хотели [23.1].
На самом деле вы можете пользоваться указателем this даже в списке инициализации конструктора [10.6], при условии что вы достаточно осторожны, чтобы по ошибке не затронуть каких-либо объектов-членов или базовых классов, которые еще не были созданы. Это требует хорошего знания деталей порядка инициализации в конструкторе, так что не говорите, что вас не предупреждали. Самое безопасное - сохранить где-нибудь значение указателя this и воспользоваться им потом. [Не понял, что они имеют в виду. - YM]
[10.8] Что такое "именованный конструктор" ("Named Constructor Idiom")?
Это техника обеспечивает более безопасный и интуитивно понятный для пользователей процесс создания для вашего класса.
Проблема заключается в том, что конструкторы всегда носят то же имя, что и их класс. Таким образом, единственное различие между конструкторами одного класса - это их список параметров. И существует множество случаев, когда разница между конструкторами становится весьма незначительной, что ведет к ошибкам.
Для использования именованных конструкторов вы объявляете все конструкторы класса в закрытом (private:) или защищенном (protected:) разделе, и пишете несколько открытых (public:) статических методов, которые возвращают объект. Эти статические методы и называются "именованными конструкторами". В общем случае существует по одному такому конструктору на каждый из различных способов создания класса.
Например, допустим, у нас есть класс Point, который представляет точку на плоскости X - Y. Существуют два распространенных способа задания двумерных координат: прямоугольные координаты (X + Y) и полярные координаты (радиус и угол). (Не беспокойтесь, если вы не разбираетесь в таких вещах, суть примера не в этом. Суть в том, что существует несколько способов создания объекта типа Point.) К сожалению, типы параметров для этих двух координатных систем одни и те же: два числа с плавающей точкой. Это привело бы к неоднозначности, если бы мы сделали перегруженные конструкторы:
    class Point {
    public:
      Point(float x, float y);     // Прямоугольные координаты
      Point(float r, float a);     // Полярные координаты (радиус и угол)
    // ОШИБКА: Неоднозначная перегруженная функция: Point::Point(float,float)
    };
    int main()
    {
      Point p = Point(5.7, 1.2);   // Неоднозначность: Какая координатная система?
    }
Одним из путей решения этой проблемы и являются именованные конструкторы:
    #include <math.h>              // Для sin() и cos()
    class Point {
    public:
      static Point rectangular(float x, float y);      // Прямоугольные координаты
      static Point polar(float radius, float angle);   // Полярные координаты
      // Эти статические члены называются "именованными конструкторами"
      // ...
    private:
      Point(float x, float y);     // Прямоугольные координаты
      float x_, y_;
    };
    inline Point::Point(float x, float y)
    : x_(x), y_(y) { }
    inline Point Point::rectangular(float x, float y)
    { return Point(x, y); }
    inline Point Point::polar(float radius, float angle)
    { return Point(radius*cos(angle), radius*sin(angle)); }
Теперь у пользователей класса Point появился способ ясного и недвусмысленного создания точек в обеих системах координат:
    int main()
    {
      Point p1 = Point::rectangular(5.7, 1.2);   // Ясно, что прямоугольные координаты
      Point p2 = Point::polar(5.7, 1.2);         // Ясно, что полярные координаты
    }
Обязательно помещайте ваши конструкторы в защищенный (protected:) раздел, если вы планируете создавать производные классы от Fred. [Видимо, ошибка. Хотели сказать - Point. - YM]
Именованные конструкторы также можно использовать том в случае, если вы хотите, чтобы ваши объекты всегда создавались динамически (посредством new [16.19]).
[10.9] Почему я не могу проинициализировать статический член класса в списке инициализации конструктора?
Потому что вы должны отдельно определять статические данные классов.
Fred.h:
    class Fred {
    public:
      Fred();
      // ...
    private:
      int i_;
      static int j_;
    };
Fred.cpp (или Fred.C, или еще как-нибудь):
    Fred::Fred()
      : i_(10),  // Верно: вы можете (и вам следует)
                 // инициализировать переменные - члены класса таким образом
        j_(42)   // Ошибка: вы не можете инициализировать
                 // статические данные класса таким образом
    {
      // ...
    }
    // Вы должны определять статические данные класса вот так:
    int Fred::j_ = 42;
[10.10] Почему классы со статическими данными получают ошибки при компоновке?
Потому что статические данные класса должны быть определены только в одной единице трансляции [10.9]. Если вы не делаете этого, вы вероятно получите при компоновке ошибку "undefined external" ("внешний объект не определен"). Например:
    // Fred.h
    class Fred {
    public:
      // ...
    private:
      static int j_;   // Объявляет статическую переменную Fred::j_
      // ...
    };
Компоновщик пожалуется ("Fred::j_ is not defined" / "Fred::j_ не определено"), если вы не напишите определение (в отличие от просто объявления) Fred::j_ в одном (и только в одном) из исходных файлов:
    // Fred.cpp
    #include "Fred.h"
    int Fred::j_ = некоторое_выражение_приводимое_к_int;
    // По-другому, если вы желаете получить неявную инициализацию нулем для int:
    // int Fred::j_;
Обычное место для определения статических данных класса Fred - это файл Fred.cpp (или Fred.C, или другое используемое вами расширение).
Секция 2 из 3 - Предыдущая - Следующая
| Вернуться в раздел "Языки C/C++/Builder" - Обсудить эту статью на Форуме | 
| Главная - Поиск по сайту - О проекте - Форум - Обратная связь |