Инициализация вектора с помощью класса, который имеет параметр конструктора передачи по ссылке и сохраняет эту ссылку как член

Я пытаюсь создать единственный экземпляр типа, который будет использоваться всеми элементами вектора. Для каждого типа элементов вектора требуется параметр передачи по ссылке в конструкторе, но я не могу заставить вектор правильно инициализироваться.

//The type of each item in the vector
class PollingConfig_t
{
    public: 
        PollingConfig_t(DebugPrinter_t& _debugPrinter) : _debugPrinter(debugPrinter) {}
    private:
        DebugPrinter_t& _debugPrinter; //Reference to the global instance of DebugPrinter_t
}

И для реальных глобальных переменных:

DebugPrinter_t DebugPrinter(true); //One instance shared among vector items
std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(DebugPrinter)); //4 instances of PollingConfig_t, each with reference to DebugPrinter.

Это не компилируется и выдает ошибку: error: use of deleted function 'PollingConfig_t& PollingConfig_t::operator=(const PollingConfig_t&)'

ОДНАКО, я могу заставить его работать так, как задумано, если просто удалю амперсанд в объявлении _debugPrinter.

    private:
        DebugPrinter_t _debugPrinter;

Но не создаст ли это копии исходного экземпляра DebugPrinter для каждого элемента вектора? Я имею в виду, что если _debugPrinter объявлен как DebugPrinter_t вместо DebugPrinter_t&, это уже не ссылка, верно?

Ссылки как члены данных в лучшем случае неприятны. Используйте указатель: DebugPrinter_t *_debugPrinter;.

Pete Becker 26.07.2024 22:19

Следует избегать использования констант и ссылочных членов. DebugPrinter_t& можно заменить на DebugPrinter_t*

NathanOliver 26.07.2024 22:20
vector выполняет много копирования, перемещения, назначения и перемещения-назначения, когда вы кладете элементы, вынимаете их, перемешиваете, сортируете и т. д., а классы со ссылками и const участниками не копируют и не назначают достаточно просто иметь по умолчанию операторы копирования, перемещения, присваивания и перемещения присваивания. Вам нужно написать код, чтобы точно сообщить программе, что делать при копировании/перемещении/назначении, а иногда вы обнаружите, что вообще не можете этого сделать.
user4581301 26.07.2024 23:19

Показанный код компилируется в gcc, clang и msvc — после исправления опечатки в вашем конструкторе PollingConfig_t. (godbolt.org/z/rEef1f4K9 ) Приведите минимально воспроизводимый пример, демонстрирующий ошибку в действии. Ошибка возникает не из-за вашей инициализации vector, поэтому она должна возникнуть из-за использования vector в дальнейшем. Как вы на самом деле используете vector, требующий копирования ваших PollingConfig_t объектов?

Remy Lebeau 26.07.2024 23:35

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

Peter 27.07.2024 03:42

Отметьте этот день. В этот день вы узнаете, что вектор может хранить только «типы значений». Это, конечно, зависит от того, как вы определяете op= и конструктор копирования для копирования данных, но есть вероятность, что, когда у вас есть ссылка внутри вашего класса, очень сложно понять, что такое копия на самом деле. Предположительно, ссылка должна быть указателем или (что маловероятно) ссылка может быть предполагаемым инвариантным объектом в течение всего времени существования объекта (следовательно, op= требует специального определения).

alfC 27.07.2024 04:54
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
92
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Как отметил в комментариях Реми Лебо, предложенный вами код компилируется, и вектор можно инициализировать таким образом.

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

В C++20 немного изменены правила. Теперь вы можете переустановить ссылки и константы, заменив весь объект. Для этого требуется относительно простой метод

doug 27.07.2024 07:00

@doug Спасибо за комментарий, я не знал, что это было представлено. Немного более элегантно, чем размещение новых вещей над собой.

Nick Matteo 28.07.2024 04:21

Ага. И constexpr, чтобы вы могли использовать его во время компиляции.

doug 29.07.2024 02:39

Вам необходимо определиться PollingConfig_t::operator=(): https://godbolt.org/z/EMWGnqvco

class PollingConfig_t {
   public:
    PollingConfig_t(DebugPrinter_t& _debugPrinter)
        : _debugPrinter(_debugPrinter) {}
    PollingConfig_t& operator=(const PollingConfig_t& p) {
        if (this != &p)
        {
            std::destroy_at(this);
            std::construct_at(this, p);
        }
        return *this;
    }

   private:
    DebugPrinter_t&
        _debugPrinter;  // Reference to the global instance of DebugPrinter_t
};

Обновлено: Спасибо за комментарий Дуга. Я исправил код.

Это не сработает, поскольку ссылка не инициализируется _debugPrinter = p._debugPrinter;. Она присваивает то, на что ссылается ссылка. Что вам нужно сделать, это заменить весь объект. Требуется C++20 или выше. См. это, чтобы узнать, как реализовать назначение копирования для объектов, содержащих константы и/или ссылки.

doug 27.07.2024 06:52
Ответ принят как подходящий

std::vector необходимо скопировать/переместить элементы в нем (например, когда он увеличен и требует перераспределения).
В вашем случае это проблема, поскольку ваш элемент содержит ссылку, которую после инициализации невозможно изменить.
Добавление operator= не является хорошим решением, поскольку у вас все равно возникнут проблемы с присвоением ссылки.
И вы правы, наличие DebugPrinter_t _debugPrinter тоже нехорошо, потому что это приведет к созданию копий.

Решить ее можно разными способами, например:

  1. Используйте указатель вместо ссылки:

    class PollingConfig_t
    {
    public: 
        PollingConfig_t(DebugPrinter_t * pDebugPrinter) : m_pDebugPrinter(pDebugPrinter) {}
    private:
        DebugPrinter_t * m_pDebugPrinter; // pointer to the global instance of DebugPrinter_t
    }
    

    Затем инициализируйте vector с помощью:

    DebugPrinter_t DebugPrinter(true); //One instance shared among vector items
    std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(&DebugPrinter)); // NOTE the & to take the address of the global object to initialize the pointer
    

    А чтобы использовать глобальный объект изнутри PollingConfig_t, используйте: m_pDebugPrinter-> ....

  2. Используйте std::shared_ptr:
    std::shared_ptr поддерживает нескольких владельцев, а также заботится о сроке службы объекта.

    class PollingConfig_t
    {
    public: 
        PollingConfig_t(std::shared_ptr<DebugPrinter_t> pDebugPrinter) : m_pDebugPrinter(pDebugPrinter) {}
    private:
        std::shared_ptr<DebugPrinter_t> m_pDebugPrinter; // shared_ptr to the global instance of DebugPrinter_t
    }
    

    Вам необходимо изменить создание глобального объекта и вектора следующим образом (обратите внимание на использование std::make_shared для создания глобального объекта):

    std::shared_ptr<DebugPrinter_t> DebugPrinter = std::make_shared<DebugPrinter_t>(true); //One instance shared among vector items
    std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(DebugPrinter)); // NOTE the shared_ptr is passed by value. It keeps a reference count on the object.
    

    Использование глобального объекта изнутри PollingConfig_t аналогично использованию необработанного указателя: m_pDebugPrinter-> ....

  3. Используйте синглтон для объекта DebugPrinter_t:
    Поскольку у вас есть только один экземпляр, общий для всех сущностей, вы можете использовать шаблон проектирования Signleton.
    Таким образом, вам даже не нужно добавлять член к каждому PollingConfig_t, потому что все элементы будут иметь доступ к синглтону.
    Я упомянул об этом в последнюю очередь, поскольку это несколько обескураживает.
    Но поскольку у вас в любом случае есть глобальный объект (что является обычным утверждением против синглетонов), он может подойти.

Другие вопросы по теме