Обход ограничений C ++ для неконстантных ссылок на временные ссылки - PullRequest
3 голосов
/ 17 сентября 2010

У меня есть структура данных 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.

Редактировать: Для ясности, использование временного объекта выполнимо, это просто неудачный беспорядок в коде, которого я хотел бы избежать. Если вы никогда не называете временное имя, оно явно используется только один раз, и чем меньше строк кода для чтения, тем лучше. Кроме того, в инициализаторах конструкторов невозможно объявить временные значения.

Ответы [ 5 ]

4 голосов
/ 17 сентября 2010

Несмотря на то, что нельзя передавать значения r в функции, принимающие неконстантные ссылки, можно вызывать функции-члены для значений r, но функция-член не знает, как она была вызвана. Если вы вернете ссылку на текущий объект, вы можете преобразовать rvalues ​​в lvalues:

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;
}
4 голосов
/ 17 сентября 2010

Проблема только в том, что это:

scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);

некрасиво? Если так, то почему бы не изменить это на это?:

auto useless_temp = factory(rng_parm);
3 голосов
/ 17 сентября 2010

Лично я бы лучше увидел const_cast, чем mutable.Когда я вижу mutable, я предполагаю, что кто-то делает логическое const -несение, и не думаю об этом.const_cast однако поднимает красные флажки, как и должно быть в коде.

Один из вариантов - использовать что-то вроде shared_ptr (auto_ptr тоже будет работать в зависимости от того, что делает factory) и передать егопо значению, что позволяет избежать затрат на копирование и поддерживать только один экземпляр, но может передаваться из фабричного метода.

0 голосов
/ 17 сентября 2010

Если вы выделите объект в куче, вы сможете преобразовать код во что-то вроде:

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.

0 голосов
/ 17 сентября 2010

Я пометил ответ FredOverflow как ответ на его предложение использовать метод для простого возврата неконстантной ссылки; это работает в C ++ 03. Для этого решения требуется метод-член для каждого типа, похожего на блокнот, но в C ++ 0x мы также можем написать этот метод более широко для любого типа:

template <typename T> T & temp(T && temporary_value) {return temporary_value;}

Эта функция просто пересылает нормальные ссылки на lvalue и преобразует ссылки на rvalue в ссылки на lvalue. Конечно, выполнение этого возвращает изменяемое значение, результат которого игнорируется - что в точности совпадает с тем, что я хочу, но может показаться странным в некоторых контекстах.

...