Подтвердить что ты не робот

Переменные ссылочного члена как члены класса

В моем месте работы я вижу, что этот стиль используется широко: -

#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;
}

Есть ли имя, чтобы описать эту идиому? Я предполагаю, что это предотвратит, возможно, большие накладные расходы на копирование большого сложного объекта?

Это вообще хорошая практика? Есть ли подводные камни для этого подхода?

4b9b3361

Ответ 1

Есть ли имя для описания этой идиомы?

В UML это называется агрегацией. Он отличается от композиции тем, что объект-член не принадлежит ссылочному классу. В С++ вы можете реализовать агрегацию двумя разными способами: с помощью ссылок или указателей.

Я предполагаю, что это предотвратит, возможно, большие накладные расходы на копирование большого сложного объекта?

Нет, это было бы очень плохой причиной для использования этого. Основной причиной агрегации является то, что содержащийся объект не принадлежит содержащемуся объекту, и поэтому их время жизни не связано. В частности, ресурс, на который ссылается объект, должен пережить соответствующий. Возможно, он был создан гораздо раньше и может жить до конца срока службы контейнера. Кроме того, состояние ссылочного объекта не контролируется классом, но может изменяться извне. Если ссылка не const, тогда класс может изменить состояние объекта, который живет за его пределами.

Это вообще хорошая практика? Есть ли подводные камни для этого подхода?

Это инструмент проектирования. В некоторых случаях это будет хорошая идея, в некоторых - нет. Наиболее распространенная ошибка заключается в том, что время жизни объекта, содержащего ссылку, никогда не должно превышать время жизни ссылочного объекта. Если окружающий объект использует ссылку после уничтожения ссылочного объекта, у вас будет поведение undefined. В общем, лучше отнести композицию к агрегации, но если вам это нужно, это будет хорошим инструментом, как и любой другой.

Ответ 2

Он вызвал инъекцию зависимостей через инъекцию конструктора: class A получает зависимость как аргумент своего конструктора и сохраняет ссылку на зависимый класс как частную переменную.

Есть интересное введение в wikipedia.

Для const-correctness я бы написал:

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.

Лучше добавить (требуется минимум С++ 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, чтобы они обращали внимание на время жизни передаваемого объекта. Забастовкa >


Несколько связанных тем:

Ответ 3

Есть ли имя для описания этой идиомы?

Нет названия для этого использования, оно просто называется "Ссылка как член класса".

Я предполагаю, что это предотвратит, возможно, большие накладные расходы на копирование большого сложного объекта?

Да, а также сценарии, в которых вы хотите связать время жизни одного объекта с другим объектом.

Это вообще хорошая практика? Есть ли подводные камни для этого подхода?

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

  • Вам необходимо убедиться, что указанный объект гарантированно существует до тех пор, пока не будет существовать объект класса.
  • Вам нужно инициализировать элемент в списке инициализатора элемента конструктора. У вас не может быть ленивая инициализация, которая может быть возможна в случае элемента-указателя.
  • Компилятор не будет генерировать назначение копирования operator=(), и вам придется предоставить его самостоятельно. Весьма сложно определить, какое действие должен предпринять оператор = в таком случае. Таким образом, в основном ваш класс становится не назначаемым.
  • Ссылки не могут быть NULL или сделаны для ссылки на любой другой объект. Если вам нужно переустановить, то это невозможно с ссылкой, как в случае с указателем.

Для большинства практических целей (если вы действительно не заинтересованы в использовании большой памяти из-за размера члена), достаточно иметь экземпляр-член, а не указатель или ссылочный элемент. Это избавляет вас от многих проблем, связанных с другими проблемами, с которыми сталкиваются ссылочные/указательные элементы, но за счет дополнительного использования памяти.

Если вы должны использовать указатель, убедитесь, что вы используете интеллектуальный указатель вместо необработанного указателя. Это сделало бы вашу жизнь намного проще с указателями.

Ответ 4

Ссылки участников обычно считаются плохими. Они делают жизнь тяжелой по сравнению с указателями-членами. Но это не особенно неузнаваемо, и это не особая идиома или вещь. Это просто наложение.

Ответ 5

С++ обеспечивает хороший механизм для управления временем жизни объекта, хотя конструкции класса/структуры. Это одна из лучших возможностей С++ над другими языками.

Когда у вас есть переменные-члены, открытые через ref или указатель, это в принципе нарушает инкапсуляцию. Эта идиома позволяет потребителю класса изменять состояние объекта A без него (A), обладающего любыми знаниями или контролем над ним. Он также позволяет потребителю удерживать указатель/указатель на внутреннее состояние, за время жизни объекта А. Это плохой дизайн. Вместо этого класс может быть реорганизован для хранения ref/указателя на общий объект (не собственный), и они могут быть установлены с использованием конструктора (Mandate rules). Класс общих объектов может быть разработан для поддержки многопоточности / concurrency, если это применимо.