返回对函数参数的引用时没有 clang 警告
No clang warning for returning a reference to function argument
我知道返回对函数参数的引用可能会调用未定义的行为,如以下示例所示。创建的第一个"MyType"在函数调用后超出范围并被销毁,导致引用悬空。
#include <iostream>
struct MyType {
std::string data;
inline ~MyType() {
data = "Destroyed!!";
}
};
const MyType& getref(const MyType& x) {
return x;
}
int main(int argc, char *argv[]) {
const MyType& test = getref(MyType {"test"});
std::cout << test.data << std::endl;
return 0;
}
我的问题是:
- 为什么没有叮当警告?有一个警告,要求将引用返回到局部变量。
- 无论我在打印之前多么努力地尝试在堆栈上分配其他内容,如果不使析构函数显式更改数据,我都无法让它打印错误的内容。这是为什么呢?
- 是否有任何返回参数引用的有效(安全)用例?
无论我在打印之前多么努力地尝试在堆栈上分配其他内容,如果不使析构函数显式更改数据,我都无法让它打印错误的内容。这是为什么呢?
你正在调用未定义的行为;此时任何事情都可能发生!
为什么没有叮当警告?有一个警告,要求将引用返回到局部变量。
是否有任何返回参数引用的有效(安全)用例?
标准::最大
假设您想要两个值的最大值:
auto foo = std::max(x, y); // foo is a copy of the result
您可能不希望返回副本(出于性能或语义原因)。幸运的是,std::max
返回一个引用,允许两种用例:
auto foo = std::max(x, y); // foo is a copy of the result
auto& bar = std::max(x, y); // bar is a reference to the result
出于语义原因,它很重要的一个例子是与std::swap
结合使用时:
std::swap(x, std::max(y, z));
想象一下,std::max
返回了一份y
.与其用y
交换x
,不如x
交换y
的副本。换言之,y
将保持不变。
分配
一个常见的用例是赋值运算符。举个例子:
#include <iostream>
class T {
public:
T(int _x) : x(_x) { }
T& operator=(const T& rhs) { x = rhs.x; return *this; }
int getX() const { return x; }
private:
int x = 0;
};
int main() {
T instanceA(42);
T instanceB(180);
std::cout << (instanceA = instanceB).getX() << std::endl;
}
您可以将隐藏的this
参数视为非空指针(对于此问题而言,与引用足够接近)。
定义复制赋值运算符本身通常被认为是偶像。之所以如此,原因之一是因为自动生成的复制赋值运算符具有该签名:
N4618 12.8.2.2 复制/移动赋值运算符 [class.copy.assign]
类的隐式声明复制赋值运算符将具有以下形式
X& X::operator=(const X&)
将此作为警告会惩罚规范代码!至于为什么人们可能想做这样的事情(除了它是规范的),那是另一个话题......
没有警告,因为const T&
很可能是对左值的引用,就像它是对右值的引用一样。
void func() {
MyType mt;
const MyType& l = getref(mt);
const MyType& r = getref(MyType{});
}
考虑到getref(mt)
是完全有效的,这给我们留下了三个选择:
- 对任何同时接受和返回
(cv) T&
的函数发出警告,无论它是用左值还是右值调用的。 这会惩罚法典,所以这不是一个好的选择。 [虽然仅在函数专门返回其参数时才发出警告会更好地实现这一点,但由于下面提到的原因,这是不可行的。 因此,如果参数和返回值是同一类型,编译器将发出错误。
[请注意,这可能会导致完美转发出现问题。 例如,std::move()
使用完美转发通过引用或右值引用获取参数,然后返回对该参数的右值引用。 任何不考虑这一点的算法都会将其视为获取和返回T&&
。 - 在向
getref()
传递右值时发出警告。 这要求编译器始终将"如果传递右值,则getref()
是UB的潜在来源"的信息保存在内存中,并且检查每个调用。 这不仅会导致额外的开销,而且实际上需要inline
getref()
(由于编译器通常设计为一次只对一个翻译单元进行操作,因此必须在编译器用于保留此信息的每个模块中定义getref()
)。 这目前是不可行的,但对于未来的编译器来说可能会变得更加实用(例如,如果跨模块优化成为标准)。 - 永远不要发出警告,假设程序员足够聪明,永远不会传递
getref()
右值,或者特别打算让getref()
成为 UB 的潜在来源。 这是最可行的选择,因为它既不会惩罚法律代码,也不需要能够跨模块优化的编译器。
因此,大多数编译器通常会选择不发出警告,假设程序员知道他们在做什么。 即使指定了-Wall
,Clang、GCC、ICC 和 MSVC 也不会为此发出任何警告。
- Clang 给了我符号更改的警告,但代码仍然产生正确的输出
- 当 noexcept 函数尝试在 gcc 或 clang 中调用非 noexcept 函数时启用警告
- 在 C++11 中轻松初始化模板类的静态成员,没有 clang 警告
- Clang-CL 警告 strnicmp 已弃用,请使用 ISO C 并C++符合标准的名称_strnicmp
- 冲突的 CLANG"虚拟 dtor"和"已弃用的复制运算符"警告
- 有没有办法在初始化字符串时避免来自 clang-tidy(fuchsia-default-arguments)的警告?
- 为什么 gcc 和 clang 都没有发出任何警告?
- Clang:覆盖之前在命令行上指定的所有警告和错误警告标志
- 为什么 Clang 警告未使用的指针和未使用的基元,而不是未使用的对象?
- clang++ 8.0.1 自分配重载警告
- Clang 对使用的类型别名发出"unused type alias"警告
- 如果对象在同一层次结构中,-Wreturn-std-move clang 警告是否正确
- 关于静态模板化 constexpr 的 Clang 警告(未定义内联函数)
- 如何禁用"不支持优化标志"的 clang 警告
- 返回对函数参数的引用时没有 clang 警告
- 为什么 clang++ 警告内联enable_if然后无法链接
- 类模板的Makefile问题:clang警告链接器输入未使用
- 没有clang警告或错误,如果c++ 11 lambda返回错误的类型
- 当我聚合初始化数组而 gcc 没有时,Clang 警告我
- 使用makefile隐藏clang警告