Обход ограничения C++ на неконстантные ссылки на временные объекты
-
03-10-2019 - |
Вопрос
У меня есть структура данных C++, которая является необходимым «блокнотом» для других вычислений.Он недолговечен и используется нечасто, поэтому производительность не критична.Однако среди других обновляемых полей отслеживания он включает генератор случайных чисел, и хотя фактическое значение генератора не важно, оно является важно, чтобы значение обновлялось, а не копировалось и использовалось повторно. Это означает, что в общем случае объекты этого класса передаются по ссылке.
Если экземпляр необходим только один раз, наиболее естественным подходом является создание его там, где это необходимо (возможно, с использованием фабричного метода или конструктора), а затем передача блокнота потребляющему методу.Сигнатуры методов потребителей используют передачу по ссылке, поскольку они не знают, что это единственный способ использования, но фабричные методы и конструкторы возвращают данные по значению - и вы не можете передавать безымянные временные объекты по ссылке.
Есть ли способ избежать засорения кода неприятными временными переменными?Я хотел бы избежать таких вещей, как следующее:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
xyz.initialize_computation(useless_temp);
Я мог бы сделать блокнот самостоятельно mutable
и просто обозначьте все параметры const &
, но мне это не кажется передовой практикой, поскольку вводит в заблуждение, и я не могу сделать это для классов, которые не полностью контролирую.Передача по ссылке rvalue потребует добавления перегрузок ко всем потребителям блокнота, что противоречит цели - иметь ясный и краткий код.
Учитывая тот факт, что производительность не критична (а вот размер кода и читаемость), каков наилучший подход к передаче такого блокнота? Использование функций C++0x допустимо, если необходимый но желательно, чтобы было достаточно функций только C++03.
Редактировать: Чтобы внести ясность, использование временного возможно, это просто досадный беспорядок в коде, которого мне хотелось бы избежать.Если вы никогда не даете временному имени имя, оно явно используется только один раз, и чем меньше строк кода нужно прочитать, тем лучше.Кроме того, в инициализаторах конструкторов невозможно объявлять временные объекты.
Решение
Хотя передавать rvalue в функции, принимающие неконстантные ссылки, недопустимо, можно вызывать функции-члены для rvalue, но функция-член не знает, как она была вызвана.Если вы вернете ссылку на текущий объект, вы можете преобразовать rvalue в lvalue:
class scratchpad_t
{
// ...
public:
scratchpad_t& self()
{
return *this;
}
};
void foo(scratchpad_t& r)
{
}
int main()
{
foo(scratchpad_t().self());
}
Обратите внимание, как вызов self()
дает выражение lvalue, хотя scratchpad_t
является значением r.
Пожалуйста, поправьте меня, если я ошибаюсь, но ссылочные параметры Rvalue не принимают ссылки lvalue, поэтому их использование потребует добавления перегрузок ко всем потребителям блокнота, что также прискорбно.
Ну, можно использовать шаблоны...
template <typename Scratch> void foo(Scratch&& scratchpad)
{
// ...
}
Если вы позвоните foo
с параметром rvalue, Scratch
будет выведено на scratchpad_t
, и поэтому Scratch&&
будет scratchpad_t&&
.
И если ты позвонишь foo
с параметром lvalue, Scratch
будет выведено на scratchpad_t&
, и из-за правил свертывания ссылок, Scratch&&
также будет scratchpad_t&
.
Заметим, что формальный параметр scratchpad
— это имя и, следовательно, lvalue, независимо от того, является ли его тип ссылкой lvalue или ссылкой rvalue.Если вы хотите пройти scratchpad
Что касается других функций, вам больше не нужен трюк с шаблоном для этих функций, просто используйте ссылочный параметр lvalue.
Кстати, вы понимаете, что временный блокнот, задействованный в xyz.initialize_computation(scratchpad_t(1, 2, 3));
будет уничтожен, как только initialize_computation
готово, да?Сохранение ссылки внутри xyz
объект для последующего пользователя было бы крайне плохой идеей.
self()
не обязательно должен быть методом-членом, это может быть шаблонная функция
Да, это тоже возможно, хотя я бы переименовал его, чтобы было понятнее:
template <typename T>
T& as_lvalue(T&& x)
{
return x;
}
Другие советы
Проблема только в этом:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
некрасиво?Если да, то почему бы не изменить это на это?:
auto useless_temp = factory(rng_parm);
Лично я бы предпочел увидеть const_cast
чем mutable
.Когда я вижу mutable
, я предполагаю, что кто-то действует логично const
-ность, и не думайте об этом много. const_cast
однако вызывает красные флажки, как и должно быть в таком коде.
Одним из вариантов было бы использовать что-то вроде shared_ptr
(auto_ptr
тоже будет работать в зависимости от того, что factory
делает) и передать его по значению, что позволяет избежать затрат на копирование и поддерживает только один экземпляр, но при этом может быть передано из фабричного метода.
Если вы разместите объект в куче, вы сможете преобразовать код во что-то вроде:
std::auto_ptr<scratch_t> create_scratch();
foo( *create_scratch() );
Фабрика создает и возвращает auto_ptr
вместо объекта в стеке.Возвращенный auto_ptr
временный объект станет владельцем объекта, но вам разрешено вызывать неконстантные методы для временного объекта, и вы можете разыменовать указатель, чтобы получить реальную ссылку.В следующей точке последовательности интеллектуальный указатель будет уничтожен, а память освобождена.Если вам нужно пройти то же самое scratch_t
к разным функциям подряд вы можете просто захватить умный указатель:
std::auto_ptr<scratch_t> s( create_scratch() );
foo( *s );
bar( *s );
Это можно заменить на std::unique_ptr
в будущем стандарте.
Я отметил ответ FredOverflow как ответ на его предложение использовать метод для простого возврата неконстантной ссылки;это работает в С++ 03.Это решение требует метода-члена для каждого типа, подобного блокноту, но в C++0x мы также можем написать этот метод в более общем смысле для любого типа:
template <typename T> T & temp(T && temporary_value) {return temporary_value;}
Эта функция просто пересылает обычные ссылки lvalue и преобразует ссылки rvalue в ссылки lvalue.Конечно, при этом возвращается изменяемое значение, результат которого игнорируется — это именно то, что мне нужно, но в некоторых контекстах может показаться странным.