Как создать временную переменную в C++

У меня есть функция, возвращающая ссылку на экземпляр моего класса «запись».

record& get_record(int key) {
    return lookup(key);
}

Это эффективно, он возвращает ссылку, а не значение. Сейчас немного модифицирую.

record& get_record(int key) {
    if (valid(key))
        return lookup(key);
    else {
        record x;
        x.valid=false;
        return x; //Here I really want to return a temporary variable
                  // and not a reference to a local variable.      
    }
}

Верно ли, что возвращать ссылку на локальную переменную не очень хорошая идея? и как мне вернуть x таким образом, чтобы он стал временной переменной?


person user2304458    schedule 25.05.2018    source источник
comment
Вы объявляете переменную вне функции. Вы передаете ссылку на свою функцию, а затем функция изменяет ее. Также это, вероятно, обман.   -  person Ian A.B. King    schedule 25.05.2018
comment
Разве вы не можете просто вернуть указатель и заставить второй случай возвращать нулевой указатель?   -  person PSkocik    schedule 25.05.2018
comment
Возможный дубликат C++ Возврат ссылки на локальную переменную   -  person Ian A.B. King    schedule 25.05.2018
comment
Вы не можете возвращать ссылки на локальные переменные. Локальная переменная уничтожается при выходе из области видимости, и ссылка становится зависшей. Однажды в прошлом у меня был похожий случай, когда я хотел вернуть ссылки. Когда соотв. метод не работает, я должен вернуть ссылку на манекен. Для этого я сделал фиктивный экземпляр static.   -  person Scheff's Cat    schedule 25.05.2018
comment
Не дубликат (этого вопроса) - речь идет о хорошем дизайне; связанный вопрос касается технических аспектов.   -  person MSalters    schedule 25.05.2018
comment
Думаю, вы пытаетесь сделать вещи более сложными, чем они могут быть. Зачем вам ссылка на возвращаемое значение? Почему не указатель.   -  person i486    schedule 25.05.2018
comment
Кроме того: идея о том, что lookup не может обрабатывать несуществующий ключ, означает, что вам, вероятно, потребуется выполнить 2 поиска: один раз, чтобы увидеть, существует ли ключ, и второй, чтобы получить значение. Если вы измените lookup так, чтобы он возвращал итератор (или умный указатель, допускающий значение NULL), вы сможете выполнить половину работы, проделанной в этой функции!   -  person UKMonkey    schedule 25.05.2018
comment
Могу я спросить, чего вы на самом деле пытаетесь достичь? (Похоже, вы хотите избежать копирования, если ключ найден.) Однако вы не получите более элегантного решения, чем возврат по значению, поэтому возникает вопрос, перевешивает ли потенциальный прирост производительности менее элегантный синтаксис вызова.   -  person Arne Vogel    schedule 25.05.2018
comment
@Arne Vogel Мой вопрос, конечно, упрощение того, что я действительно пытаюсь сделать. У меня есть две базы данных new_db и old_db. new_db будет создан из ключей, ссылающихся на части old_db, которые будут скопированы, и если для данного ключа не будет найдено ни одной части, она будет построена и добавлена ​​в new_db. Я подумал, что было бы разумно, если бы get_record отвечала не только за поиск записи, но и за создание той, которую не смогла найти на основе значения ключа и некоторых других параметров. Однако теперь я понимаю, что, возможно, мне следует выбрать другой дизайн.   -  person user2304458    schedule 25.05.2018


Ответы (5)


Это хуже, чем плохая идея, это неопределенное поведение и в большинстве случаев приводит к сбою. Это плохо (ТМ).

Что вы можете сделать, так это изменить тип возвращаемого значения get_record, чтобы он возвращал интеллектуальный указатель. Если key действителен, он возвращает на него указатель наблюдателя. В противном случае он возвращает интеллектуальный указатель владельца на только что созданный объект:

#include <memory>
#include <iostream>

struct record { int n; } some_record{42};

std::shared_ptr<record> get_record(bool b)
{
    if (b == true) {
        return std::shared_ptr<record>{&some_record, [](record*){}}; // see explanation ^1
    }
    return std::shared_ptr<record>{new record{0}};
}

int main()
{
    std::cout << get_record(true)->n << "\n";  // this is some_record
    std::cout << get_record(false)->n << "\n"; // this is a temporary
}

1) О [](record*){}: эта лямбда-выражение без операций передается в качестве второго аргумента std::shared_ptr::shared_ptr() вызывается при уничтожении интеллектуального указателя. Он заменяет средство удаления по умолчанию std::shared_ptr, поведение которого заключается в вызове delete для принадлежащего указателя.


О том, почему ваш дизайн несовершенен. На самом деле, если get_record возвращает ссылку, это делает ее несогласованной. Что вы хотите:

  • если key действителен, верните ссылку на существующий/постоянный объект и
  • в противном случае вернуть временный объект.

Эти два понятия взаимоисключающие, и ваша функция не имеет смысла: что семантически возвращает get_record?

person YSC    schedule 25.05.2018
comment
Вы действительно можете вернуть shared_ptr<record>. Вам не нужно делать различие между наблюдателем/владельцем. Различие будет заключаться в том, что для недопустимых ключей возвращаемый shared_ptr является исключительным владельцем. - person MSalters; 25.05.2018
comment
@MSalters да, очевидно ^^ см. обновление (здесь еще рано) - person YSC; 25.05.2018
comment
немного педантичен, но возврат ссылки на временный объект не является неопределенным поведением, а его использование впоследствии. - person UKMonkey; 25.05.2018
comment
@UKMonkey Это верно, но я не хочу вдаваться в такие подробности в этом ответе. Я мог бы перегибаться к этому, хотя, сделаю, спасибо. - person YSC; 25.05.2018
comment
@YSC Не могли бы вы объяснить использование лямбда в случае b == true ? Думаю, мне нравится, что происходит, но я просто не совсем понимаю ... Я думаю, что это должен быть параметр &some_record? - я также думаю, что я далеко :о - person code_fodder; 25.05.2018
comment
@YSC аааа ... круто, спасибо ... я думаю, вы не хотите, чтобы shared_ptr удалял some_record (не знаю, что произойдет в этом случае! ... умные вещи :) - person code_fodder; 25.05.2018

Верно ли, что возвращать ссылку на локальную переменную не очень хорошая идея?

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

Вы можете сделать x переменной static.

record& get_record(int key) {
    if (valid(key))
        return lookup(key);
    else {
        static record x;
        x.valid=false;
        return x;
    }
}

Обратите внимание, что возвращаемая ссылка всегда будет относиться к одному и тому же объекту, то есть x.

person songyuanyao    schedule 25.05.2018
comment
Это, конечно, вызывает проблемы, если вы вызываете его дважды, то есть Foo(get_record(1), get_record(2)) создаст две ссылки на один и тот же x. - person MSalters; 25.05.2018
comment
@MSalters Да, эта концепция подходит намного лучше, если возвращается ссылка const. По крайней мере, элемент valid можно было бы сделать const, чтобы предотвратить случайный доступ немного лучше. - person Scheff's Cat; 25.05.2018
comment
x не является временным, как спрашивает ОП. - person YSC; 25.05.2018
comment
@YSC Я не думаю, что OP хочет временного (как определено термином C ++), я полагаю, он просто хочет вернуть что-то для ложного случая, который можно было бы использовать снаружи для определения. - person songyuanyao; 25.05.2018
comment
Пожалуйста, не пишите код, который компилируется, но имеет ужасные скрытые последствия, по крайней мере, не указывая на эти последствия в больших жирных предупреждающих сообщениях. всегда будет ссылаться на один и тот же объект, просто не имеет такого же влияния, поскольку вы получите жуткое действие на расстоянии, если вы вызовете функцию дважды, или это приведет к гонкам данных и превратит всю вашу программу в неопределенное поведение. - person Sebastian Redl; 25.05.2018
comment
@SebastianRedl Я могу понять гонки данных, но что вы имеете в виду под жутким действием, когда дважды вызываете функцию? Я знаю, что у него есть потенциальные проблемы, но это зависит от сценария OP; без подробностей мы не можем дать более специальные и полные предупреждающие сообщения. - person songyuanyao; 25.05.2018
comment
Я думаю, что идеалом была бы временная переменная, но как насчет дополнительной функции, которая создает временную переменную. запись make_temporary (запись &a) { return a; } Вместо того, чтобы просто возвращать x, я верну make_temporay(x); - person user2304458; 25.05.2018
comment
@ user2304458 Вы не можете этого сделать. Временные будут немедленно уничтожены. И мы не можем временно привязать неконстантную ссылку. - person songyuanyao; 25.05.2018
comment
@usersongyuanyao Функция возвращает либо указатель, либо ссылку, либо значение. Если он возвращает значение, создается временная переменная, если я правильно понимаю, поэтому моя функция make_temporary вернет временную переменную. - person user2304458; 25.05.2018
comment
@user2304458 user2304458 Да, он возвращает временное значение. Но вы не можете привязать его к неконстантной ссылке, поэтому вы не можете вернуть его в функцию, которая возвращает неконстантную ссылку. Пример - person songyuanyao; 25.05.2018
comment
@usersongyuanyao Спасибо, что вы указали на это, но на самом деле я собираюсь использовать ссылку на константу :) - person user2304458; 25.05.2018
comment
@user2304458 user2304458 Обратите внимание, что временная ссылка будет немедленно уничтожена, поэтому вы можете сделать ее const ссылкой, но возвращаемая ссылка по-прежнему будет висящей ссылкой. - person songyuanyao; 25.05.2018

Если вам разрешено изменять функцию get_record, вы можете изменить тип возвращаемого значения на указатель на запись вместо ссылки на запись.

record* get_record( int key )
{
    if( valid( key ) ) return &lookup( key ); 
    else               return nullptr;
 }

Однако этот подход требует двух гарантий:

  • функция lookup должна возвращать ссылку на запись
  • запись, возвращаемая поиском, должна оставаться активной, когда поиск возвращает результат (например, запись является элементом какого-то контейнера, а поиск возвращает свою ссылку)
person Guido Niewerth    schedule 25.05.2018

Поскольку другие уже подробно объясняют, почему возвращать ссылку на локальный объект плохо, я просто предоставлю альтернативу: исключения. Хотя вы могли бы написать пользовательское исключение, вероятно, исключение std::out_of_range могло бы оказаться уместным. (поскольку ключ находится вне диапазона допустимых ключей, что и является std::map::at делает).

Взгляните на: Как создать исключение C++

record& get_record(int key)
{
  if (valid(key))
  {
    return lookup(key);
  } else {
    throw std::out_of_range("The provided key is invalid");
  }
}

Теперь вам, очевидно, придется перехватывать исключение в коде, вызывающем get_record, иначе ваша программа все равно завершится.

person Lanting    schedule 25.05.2018

Возврат ссылки сам по себе не приводит к неопределенному поведению, но если вы попытаетесь изменить его, вы это сделаете.

Доступ к объекту за пределами его времени жизни является неопределенным поведением.

int* foo(void) {
    int a = 17; // a has automatic storage duration
    return &a;
}  // lifetime of a ends
int main(void) {
    int* p = foo(); // p points to an object past lifetime ("dangling pointer")
    int n = *p; // undefined behavior
}

http://en.cppreference.com/w/c/language/lifetime

Если у вас есть доступ к C++17, вы можете реализовать его с помощью std::Optional< /а>. Обратите внимание на использование std::reference_wrapper, поскольку использование ссылки in std::optional делает вашу программу неправильной.

std::optional<std::reference_wrapper<record>> get_record(int key) {
    if (valid(key))
        return std::optional<std::reference_wrapper<record>>(lookup(key));
    else
        return std::nullopt;
}

Без С++ 17 вы могли бы просто вернуть указатель на свою запись:

record* get_record(int key) {
    if (valid(key))
        return &lookup(key);
    else
        return nullptr;
}

Или, если вы предпочитаете, вы можете сохранить возвращаемый тип ссылки и создать исключение, указывающее на отсутствующую запись. Хотя это мой наименее предпочтительный подход, так как он позволяет легко вызывать get_record без переноса в try / catch.

record& get_record(int key) {
    if (valid(key))
        return &lookup(key);
    else
        throw std::out_of_range("Invalid record key!");
}
person Mark Ingram    schedule 25.05.2018