Посилання на змінні члени як члени класу


85

На своєму робочому місці я бачу цей стиль, який широко використовується: -

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

Чи існує назва, що описує цю ідіому? Я припускаю, що це для запобігання можливим великим накладним витратам на копіювання великого складного об'єкта?

Це загалом хороша практика? Чи є у цього підходу підводні камені?


4
Однією з можливих помилок є те, що об’єкт, на який ви маєте посилання у змінній-члені, буде знищений в іншому місці, і ви спробуєте отримати до нього доступ через свій клас
математик1975,

Відповіді:


116

Чи існує назва, що описує цю ідіому?

В UML це називається агрегацією. Він відрізняється від композиції тим, що об’єкт-член не належить референтному класу. У C ++ ви можете реалізувати агрегування двома різними способами, за допомогою посилань або покажчиків.

Я припускаю, що це для запобігання можливим великим накладним витратам на копіювання великого складного об'єкта?

Ні, це було б дійсно поганою причиною використовувати це. Основною причиною агрегації є те, що вміщений об'єкт не належить вміщуваному об'єкту, і, отже, їх життєвий цикл не пов'язаний. Зокрема, час життя об’єкта, на який посилається, повинен пережити той, на який йде посилання. Можливо, він був створений набагато раніше і міг жити після закінчення терміну служби контейнера. Крім того, стан об'єкта, на який посилається, не контролюється класом, але може змінюватися зовні. Якщо посилання немає const, тоді клас може змінити стан об'єкта, який живе поза ним.

Це загалом хороша практика? Чи є у цього підходу підводні камені?

Це інструмент проектування. У деяких випадках це буде гарна ідея, в деяких - ні. Найбільш поширеною помилкою є те, що час життя об’єкта, що містить посилання, ніколи не повинен перевищувати час життя об’єкта, на який посилається. Якщо об'єкт, що включає, використовує посилання після знищення об'єкта, на який посилається, у вас буде невизначена поведінка. Загалом краще віддати перевагу композиції, а не агрегації, але якщо вона вам потрібна, це такий же хороший інструмент, як і будь-який інший.


7
"Ні, це було б дуже поганою причиною використовувати це". Не могли б Ви детальніше розказати це питання? Що можна використовувати замість цього для досягнення цього?
coincoin

@coincoin: Щоб досягти чого саме?
Девід Родрігес - dribeas

3
to prevent the possibly large overhead of copying a big complex object?
coincoin

3
@underscore_d дякую за вашу відповідь. Тоді що відбувається, коли ви не можете використовувати жоден з них. Уявіть, що ми хочемо поділитися одним і тим же об’єктом у різних класах. У вас вийде копія об’єкта для кожного класу, якщо ви передасте цей об’єкт-член за значенням? Тож рішення полягає у використанні розумних покажчиків або посилань, щоб уникнути копіювання. Ні ?
coincoin

2
@ plats1 ось що я щойно писав, я це знаю. Я хочу сказати, що ви можете використовувати розумні вказівники або посилання.
coincoin

37

Це називається введенням залежностей за допомогою введення конструктора : клас Aотримує залежність як аргумент для свого конструктора і зберігає посилання на залежний клас як приватну змінну.

У Вікіпедії є цікавий вступ .

Для коректності правильності я б написав:

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

але проблема цього класу полягає в тому, що він приймає посилання на тимчасові об'єкти:

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

Краще додати (потрібен принаймні C ++ 11):

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

У будь-якому випадку, якщо ви зміните конструктор:

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

майже гарантовано, що у вас не буде вказівника на тимчасовий .

Крім того, оскільки конструктор бере вказівник, користувачам зрозуміліше, Aщо їм потрібно звертати увагу на час життя об’єкта, який вони передають.


Дещо пов’язані теми:


20

Чи існує назва, що описує цю ідіому?

Для цього використання немає назви, воно просто відоме як "Посилання як член класу" .

Я припускаю, що це для запобігання можливим великим накладним витратам на копіювання великого складного об'єкта?

Так, а також сценарії, коли ви хочете пов’язати час життя одного об’єкта з іншим об’єктом.

Це загалом хороша практика? Чи є у цього підходу підводні камені?

Залежить від вашого використання. Використання будь-якої мовної функції подібно до "вибору коней на курси" . Важливо зазначити, що кожна ( майже вся ) мовна функція існує, оскільки вона корисна у деяких сценаріях.
Є кілька важливих моментів, на які слід звернути увагу при використанні посилань як учасників класу:

  • Вам потрібно переконатися, що згаданий об’єкт гарантовано існуватиме доти, поки не існує об’єкт вашого класу.
  • Вам потрібно ініціалізувати елемент у списку ініціалізатора елемента конструктора. Ви не можете мати ледачу ініціалізацію , яка може бути можливою у випадку члена покажчика.
  • Компілятор не генерує призначення копії, operator=()і вам доведеться надати його самостійно. Трудно визначити, які дії вчинитиме ваш =оператор у такому випадку. Отже, в основному ваш клас стає недоступним .
  • Посилання не можуть бути NULLзроблені або зроблені для посилання на будь-який інший об'єкт. Якщо вам потрібна перестановка, тоді це неможливо з посиланням, як у випадку з покажчиком.

Для більшості практичних цілей (якщо ви насправді не стурбовані великим використанням пам'яті через розмір члена), достатньо мати лише екземпляр члена, замість вказівника або члена посилання. Це економить вам багато турбот щодо інших проблем, які члени посилання / покажчика приносять із собою, хоча за рахунок додаткового використання пам'яті.

Якщо вам потрібно використовувати вказівник, переконайтеся, що ви використовуєте розумний вказівник замість необробленого вказівника. Це значно полегшить вам життя за допомогою покажчиків.


" Я припускаю, що це для запобігання можливим великим накладним витратам на копіювання великого складного об'єкта? Так" - будь ласка, детальніше поясніть, чому, на вашу думку, цей шаблон має якесь значення для уникнення копіювання, оскільки я не бачу жодного. якщо ця не-ідіома як-небудь зберігає копії для когось як побічний ефект від її реальних цілей, то їх оригінальний дизайн спочатку був фатально помилковим, і просто заміна його на місці з цим шаблоном навряд чи зробить те, що вони очікують .
underscore_d

@underscore_d Скажімо, класу потрібна нетривіальна кількість даних const, і одночасно може бути багато екземплярів цього класу, для кожного екземпляра може бути неприпустимо марно мати власну копію даних const. Таким чином, збереження посилання const на зовнішнє розташування цих даних, якими можна поділитися, економить багато копіювання. shared_ptr не обов'язково є рішенням, оскільки дескриптор даних не обов'язково динамічно розподіляється.
важливо

@Unimportant Я не впевнений, проти чого було моє заперечення ще в 2016 році. Я постійно використовую посилання як учасники класу. Можливо, я був стурбований тим, що проста заміна власності, що має значення, на посилання, призведе до життєвих питань, і це не обов'язково панакея чи щось, що завжди можна зробити 1: 1. Я не знаю.
underscore_d

1

C ++ забезпечує хороший механізм управління часом життя об'єкта за допомогою класів / структурних конструкцій. Це одна з найкращих можливостей C ++ перед іншими мовами.

Коли у вас є змінні члена, виставлені через ref або покажчик, це в принципі порушує інкапсуляцію. Ця ідіома дозволяє споживачеві класу змінювати стан об’єкта A, не маючи при цьому жодного знання або контролю над ним (A). Це також дозволяє споживачеві триматися за посилання / вказівник на внутрішній стан А, поза часом життя об'єкта А. Це поганий дизайн. Натомість клас може бути рефакторований, щоб утримувати ref / покажчик на спільний об'єкт (не володіти ним), і їх можна встановити за допомогою конструктора (Надати правила часу життя). Клас спільного об'єкта може бути розроблений для підтримки багатопоточності / паралельності, залежно від обставин.


2
але код в OP не містить посилання на будь-яку змінну-член. він має змінну-член, яка є посиланням. отже, чудові моменти, але, здавалося б, не пов’язані.
underscore_d

-3

Посилання на членів зазвичай вважаються поганими. Вони ускладнюють життя порівняно з покажчиками членів. Але це не особливо незвично, і це не якась спеціальна ідіома чи річ. Це просто псевдонім.


23
Чи можете ви надати посилання на підтримку , яку зазвичай вважають поганою ?
Девід Родрігес - dribeas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.