Правильно ли это правило С++ статического анализа имеет смысл?
Я реализую некоторые правила статического анализа С++, и один из них запрещает функции возвращать ссылку или указатель на ссылочный параметр функции, то есть все несоответствующие:
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);
Ответы
Ответ 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, где мы возвращаем ссылка, но нет ничего хорошего, чтобы вернуть ссылку, и язык не позволяет ссылаться на нуль.)