Есть ли предупреждение C ++ для возврата ссылки во временный файл? - PullRequest
0 голосов
/ 24 мая 2018

В этом случае есть ошибка:

const int& foo() {
    const int x = 0;
    return x;
}

и даже

const int& foo() {
    const std::pair<int,int> x = {0,0};
    return x.first;
}

, но не это:

const int& foo() {
    const std::array<int,1> x = {0};
    return x[0];
}

и (что не удивительно) не это:

const int& foo() {
    const std::vector<int> x = {0};
    return x[0];
}

В частности, в случае std::vector я получаю, что это предупреждение было бы довольно хитрым, поскольку для компилятора не очевидно, что const int&, возвращаемый std::vector<int>::operator[](size_t) const, является ссылкой навременный характер.Я на самом деле немного удивлен тем, что std::array не дает сбоя, хотя, так как этот похожий случай действительно дает мне ошибку:

struct X {
    int x[0];
};

const int& foo() {
    X x;
    return x.x[0];
}

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

Я споткнулся об этом с помощью чего-то вроде следующего, в котором я встроил цепочку вызовов, но поскольку C ++ позволяет назначать локальные данные для const&, подробная версия работает, в то время как внешне идентичная версия сразу удаляет временную, оставляя висящую ссылку:

#include <iostream>

struct A {
    int x = 1234;
    A() { std::cout << "A::A " << this << std::endl; }
    ~A() { x = -1; std::cout << "A::~A " << this << std::endl; }
    const int& get() const { return x; }
};

struct C { 
    C() { std::cout << "C::C " << this << std::endl; }
    ~C() { std::cout << "C::~C " << this << std::endl; }
    A a() { return A(); }
};

int foo() {
    C c;
    const auto& a = c.a();
    const auto& x = a.get();
    std::cout << "c.a(); a.get() returning at " << &x << std::endl;
    return x;
}

int bar() {
    C c;
    const int& x = c.a().get();
    std::cout << "c.a().get() returning at " << &x << std::endl;
    return x;
}

int main() {
    std::cout << foo() << std::endl;
    std::cout << bar() << std::endl;
}

, которая выводит

C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
c.a(); a.get() returning at 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
1234
C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
c.a().get() returning at 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
-1

Ответы [ 2 ]

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

Использование значения, жизнь которого закончилась, является неопределенным поведением, см. [basic.life] /6.1.Стандарт не требует компилятора для вывода любой диагностики для UB.

Таким образом, диагностика, которую вы видите, является просто любезностью компилятора.Приятно видеть, что вы получаете некоторых из них, но они, безусловно, далеко не водонепроницаемы, как вы заметили.

И да, продление срока службы не является цепным.Это делает его очень опасным и ненадежным.

Вы можете попробовать Clang's Address Sanitizer (ASAN).

На самом деле ASAN, похоже, улавливает вашу проблему (-fsanitize-address-use-after-scope):

==35463==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fffffffe970 at pc 0x000000498d53 bp 0x7fffffffe910 sp 0x7fffffffe908
READ of size 4 at 0x7fffffffe970 thread T0
0 голосов
/ 24 мая 2018

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

Ваш код совсем не идентичен.В первом случае:

const auto& a = c.a();
const auto& x = a.get();

время жизни продлено на время жизни константной ссылки, поэтому x действует до тех пор, пока действует a, но во втором:

const int& x = c.a().get();

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

Решение для вашего случая может быть сделано дизайнером класса A:

struct A {
    int x = 1234;
    A() { std::cout << "A::A " << this << std::endl; }
    ~A() { x = -1; std::cout << "A::~A " << this << std::endl; }
    const int& get() const & { return x; }
    int get() && { return x; } // prevent dangling reference or delete it to prevent compilation
};
...