Подтвердить что ты не робот

Правильно ли это правило С++ статического анализа имеет смысл?

Я реализую некоторые правила статического анализа С++, и один из них запрещает функции возвращать ссылку или указатель на ссылочный параметр функции, то есть все несоответствующие:

int *f(int& x) { return &x; } // #1
const int *g(const int& x) { return &x; } // #2
int& h(int& x) { return x; } // #3
const int& m(const int& x) { return x; } // #4

Обоснование, данное для этого, заключается в том, что "это поведение, определяемое реализацией, является ли ссылочный параметр временным объектом или ссылкой на параметр".

Я озадачен этим, однако, потому что потоковые операторы в С++ написаны таким образом, например

std::ostream& operator<<(std::ostream& os, const X& x) {
    //...
    return os;
}

Я думаю, что я уверен, что потоковые операторы на С++ вообще не демонстрируют поведение, определяемое реализацией, и что происходит?

По моему мнению, как и сейчас, я ожидал бы, что # 1 и # 3 будут хорошо определены, исходя из того, что временные лица не могут быть привязаны к неконстантным ссылкам, поэтому int& x относится к реальному объекту который имеет продолжительность жизни за пределами функции, поэтому возвращение указателя или ссылки на этот объект в порядке. Я ожидал бы, что # 2 будет изворотливым, потому что временное могло быть привязано к const int& x, и в этом случае попытка найти его адрес окажется плохим. Я не уверен в # 4 - мое чувство кишки в том, что это тоже потенциально опасно, но я не уверен. В частности, я не знаю, что произойдет в следующем случае:

const int& m(const int& x) { return x; }
//...
const int& r = m(23);
4b9b3361

Ответ 1

Как вы говорите, # 1 и # 3 хороши (хотя # 1 - плохой стиль).

# 4 является хитрой по той же причине # 2; он позволяет распространять константную ссылку на временное время жизни.

Проверить:

#include <iostream>

struct C {
  C() { std::cout << "C()\n"; }
  ~C() { std::cout << "~C()\n"; }
  C(const C &) { std::cout << "C(const C &)\n"; }
};

const C &foo(const C &c) { return c; }

int main() { 
   const C &c = foo(C());
   std::cout << "c in scope\n";
}

Выводится:

C()
~C()
c in scope

Ответ 2

В С++ 11, # 2 и # 4 можно сделать безопасными, если есть также избыточные опорные перегрузки. Таким образом:

const int *get( const int &x ) { return &x; }
const int *get( const int &&x ) { return nullptr; }

void test() {
    const int x = 0;
    const int *p1 = get( x ); // OK; p1 is &x.
    const int *p2 = get( x+42 ); // OK; p2 is nullptr.
}

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

(Возможно, более безопасным было бы, если ссылочная перегрузка const rvalue была закрыта, оставлена ​​ undefined или иначе вызвана ошибкой времени компиляции или времени ссылки. Это особенно верно для случая №4, где мы возвращаем ссылка, но нет ничего хорошего, чтобы вернуть ссылку, и язык не позволяет ссылаться на нуль.)