2010-09-16 9 views
3

У меня есть структура данных C++, которая является необходимой «блокнотной» для других вычислений. Это не долговечно, и это не часто используется, поэтому производительность не критична. Тем не менее, он включает генератор случайных чисел среди других обновляемых полей отслеживания, и, хотя фактическое значение генератора не имеет значения, является важным, чтобы значение обновлялось, а не копировалось и использовалось повторно. Это означает, что в общем случае объекты этого класса передаются по ссылке.Работа с ограничениями на 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-только функции должны быть достаточными.

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

+0

Можете ли вы взять «блокнот» по значению вместо ссылки? Таким образом, это может быть временным и изменчивым. –

+0

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

+0

@ Eamon Nerbonne: Потенциально, только концептуально. В оптимизированных сборках во многих компиляторах параметр будет создан «на месте». –

ответ

4

Хотя не имеет значения передавать значения значениям для функций, принимающих неконстантные ссылки, нормально вызывать функции-члены на rvalues, но функция-член не знает, как она была вызвана.Если вы возвращаете ссылку на текущий объект, вы можете конвертировать 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 является Rvalue.

Пожалуйста, исправьте меня, если я ошибаюсь, но ссылочные параметры 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; 
} 
+0

Я понимаю, что он будет немедленно уничтожен. Блокнот имеет только набор значений и хранения, необходимых для инициализации. Шаблоны не являются привлекательным вариантом, так как они потребуют увеличения размера файла заголовка совсем немного, и дело в том, чтобы улучшить читаемость. С другой стороны, функция self() кажется интересной ... –

+0

И это решает мою проблему: self() не обязательно должен быть элементом-членом, это может быть шаблонная функция ala 'std :: move', конечно ! –

+0

@ Eamon: Хорошая идея, я обновил свой пост. – fredoverflow

4

ли проблема только что:

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

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

auto useless_temp = factory(rng_parm); 
+0

@PigBen: Потому что 'auto' еще не в стандарте, и у некоторых из нас нет роскоши быть в состоянии выйти за пределы стандарта. –

+0

Ну, это одна из причин, но я просто хотел бы избежать бессмысленной строки кода, а в списке инициализаторов конструктора я действительно * не могу * объявить временное. –

+0

@Billy: Он сказал: «Использование функций C++ 0x в случае необходимости». И если нет другого хорошего решения, то это необходимо. –

3

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

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

+0

'shared_ptr' звучит разумно, хотя это будет означать использование его во всем API, что, возможно, является более сложным, чем избегать временного. Как поможет 'const_cast'? –

+0

Какое официальное слово используется при использовании 'const_cast' во временных рядах? Моя кишка говорит, что это UB, но я не уверен. Практически говоря, я не могу себе представить, что он не работает над сложными типами, но мне любопытно, какие юридические последствия. –

+0

@Dennis: Вы не используете его во временном, вы используете его на членах временного. –

0

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

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

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

0

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

std::auto_ptr<scratch_t> create_scratch(); 

foo(*create_scratch()); 

Фабрика создает и возвращает auto_ptr вместо объекта в стеке. Возвращенный auto_ptr временный будет владеть объектом, но вам разрешено вызывать методы non-const во временном режиме, и вы можете разыменовать указатель, чтобы получить реальную ссылку.В следующей точке последовательности умный указатель будет уничтожен и память освободится. Если вам необходимо пройти ту же scratch_t различные функции в ряд, вы можете просто захватить смарт-указатель:

std::auto_ptr<scratch_t> s(create_scratch()); 
foo(*s); 
bar(*s); 

Это может быть заменен std::unique_ptr в предстоящем стандарте.

+0

Метод factory может быть конструктором, но в противном случае это звучит так, как будто он будет работать в любое время; по сути, это предложение к предложению FredOverflow, если оно реализовано несколько иначе: вы используете метод (здесь перегрузка оператора), чтобы вернуть ссылку на обход обычного ограничения на ссылки rvalue. Фактически, вы могли бы заменить auto_ptr простой структурой обтекания и неявным оператором литья - правильно? –

+0

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