Есть ли предупреждение C++ для возврата ссылки во временное хранилище?

Для этого случая есть ошибка:

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
ча (); a.get() возвращается в 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
1234
C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
ca().get() возвращается в 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
-1

2 ответа

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

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

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
};

Использование значения, жизнь которого закончилась, является неопределенным поведением, см. [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
Другие вопросы по тегам