Я пытаюсь создать единственный экземпляр типа, который будет использоваться всеми элементами вектора. Для каждого типа элементов вектора требуется параметр передачи по ссылке в конструкторе, но я не могу заставить вектор правильно инициализироваться.
//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_t*
vector выполняет много копирования, перемещения, назначения и перемещения-назначения, когда вы кладете элементы, вынимаете их, перемешиваете, сортируете и т. д., а классы со ссылками и const участниками не копируют и не назначают достаточно просто иметь по умолчанию операторы копирования, перемещения, присваивания и перемещения присваивания. Вам нужно написать код, чтобы точно сообщить программе, что делать при копировании/перемещении/назначении, а иногда вы обнаружите, что вообще не можете этого сделать.
Показанный код компилируется в gcc, clang и msvc — после исправления опечатки в вашем конструкторе PollingConfig_t. (godbolt.org/z/rEef1f4K9 ) Приведите минимально воспроизводимый пример, демонстрирующий ошибку в действии. Ошибка возникает не из-за вашей инициализации vector, поэтому она должна возникнуть из-за использования vector в дальнейшем. Как вы на самом деле используете vector, требующий копирования ваших PollingConfig_t объектов?
Ссылочные члены являются проблематичными, поскольку после инициализации для ссылки на объект ссылку нельзя изменить (например, посредством присваивания) для ссылки на другой объект. Это основная причина, по которой наличие ссылочного члена приводит к тому, что класс имеет оператор присваивания deleted. Это противоречит требованию стандартных контейнеров, содержащих объекты, имеющие семантику копирования и/или перемещения (конструктор и оператор присваивания). Если необходимо, используйте вместо этого член-указатель (но имейте в виду, что сгенерированное по умолчанию присваивание/копия будет выполнять поверхностные, а не глубокие копии).
Отметьте этот день. В этот день вы узнаете, что вектор может хранить только «типы значений». Это, конечно, зависит от того, как вы определяете op= и конструктор копирования для копирования данных, но есть вероятность, что, когда у вас есть ссылка внутри вашего класса, очень сложно понять, что такое копия на самом деле. Предположительно, ссылка должна быть указателем или (что маловероятно) ссылка может быть предполагаемым инвариантным объектом в течение всего времени существования объекта (следовательно, op= требует специального определения).





Как отметил в комментариях Реми Лебо, предложенный вами код компилируется, и вектор можно инициализировать таким образом.
Ошибка вызвана чем-то другим в использовании вектора. Компилятор говорит, что вам необходимо предоставить оператор присваивания копии, поскольку компилятор не может синтезировать его для ссылочного члена. Вы не можете повторно разместить ссылку, что усложняет задачу, поэтому, если вам действительно нужно выполнять назначения, вы можете вместо этого использовать указатель.
В C++20 немного изменены правила. Теперь вы можете переустановить ссылки и константы, заменив весь объект. Для этого требуется относительно простой метод
@doug Спасибо за комментарий, я не знал, что это было представлено. Немного более элегантно, чем размещение новых вещей над собой.
Ага. И constexpr, чтобы вы могли использовать его во время компиляции.
Вам необходимо определиться 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 или выше. См. это, чтобы узнать, как реализовать назначение копирования для объектов, содержащих константы и/или ссылки.
std::vector необходимо скопировать/переместить элементы в нем (например, когда он увеличен и требует перераспределения).
В вашем случае это проблема, поскольку ваш элемент содержит ссылку, которую после инициализации невозможно изменить.
Добавление operator= не является хорошим решением, поскольку у вас все равно возникнут проблемы с присвоением ссылки.
И вы правы, наличие DebugPrinter_t _debugPrinter тоже нехорошо, потому что это приведет к созданию копий.
Решить ее можно разными способами, например:
Используйте указатель вместо ссылки:
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-> ....
Используйте 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-> ....
Используйте синглтон для объекта DebugPrinter_t:
Поскольку у вас есть только один экземпляр, общий для всех сущностей, вы можете использовать шаблон проектирования Signleton.
Таким образом, вам даже не нужно добавлять член к каждому PollingConfig_t, потому что все элементы будут иметь доступ к синглтону.
Я упомянул об этом в последнюю очередь, поскольку это несколько обескураживает.
Но поскольку у вас в любом случае есть глобальный объект (что является обычным утверждением против синглетонов), он может подойти.
Ссылки как члены данных в лучшем случае неприятны. Используйте указатель:
DebugPrinter_t *_debugPrinter;.