Главная > Программирование > Языки 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" - Обсудить эту статью на Форуме |
Главная - Поиск по сайту - О проекте - Форум - Обратная связь |