(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Краткий вопросник по C++. Часть 2 (FAQ)

Источник: hardline
Автором английской версии является Marshall Cline (cline@parashift.com), автором перевода - Ярослав Миронов (slava_mironov@mail.ru)

[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, или другое используемое вами расширение).

Читать часть 3

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 26.08.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
ABBYY Lingvo x6 Английская Домашняя версия, электронный ключ
SmartBear AQtime Pro - Node-Locked License (Includes 1 Year Maintenance)
SmartBear QAComplete Concurrent User Subscription License - On Premise (1 Year Subscription)
Купить Антивирус Dr.Web Server Security Suite для сервера
Quest Software. TOAD Professional Edition
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Программирование на Microsoft Access
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
СУБД Oracle "с нуля"
eManual - электронные книги и техническая документация
Windows и Office: новости и советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100