Короткий запитальник по C + +. Частина 2 (FAQ), Різне, Програмні керівництва, статті

[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  / / Для 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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*