Как создать временную переменную в C ++ - PullRequest
0 голосов
/ 25 мая 2018

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

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 таким образом, чтобы он стал временной переменной?

Ответы [ 5 ]

0 голосов
/ 25 мая 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 :: необязательно .Обратите внимание на использование std :: reference_wrapper , потому что использование ссылки в 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;
}

Без C ++ 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!");
}
0 голосов
/ 25 мая 2018

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

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

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

  • функция lookup должна возвращать ссылку на запись
  • запись, возвращаемая при поиске, должна все еще существовать, когда поиск возвращается (например, запись является элементом некоторогосортировка контейнера и поиск возвращает его ссылку)
0 голосов
/ 25 мая 2018

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

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


О том, почему ваш дизайн имеет недостатки.Фактически, get_record возвращает ссылку, что делает ее непоследовательной.То, что вы хотите:

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

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

0 голосов
/ 25 мая 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, иначе ваша программа все равно будет остановлена.

0 голосов
/ 25 мая 2018

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

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

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

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

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

...