我理解返回对函数参数的引用可以调用未定义的行为,如下例所示。在函数调用之后创建的第一个“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;
}
我的问题是:
答案 0 :(得分:3)
无论我在打印之前如何努力在堆栈上分配其他东西,我都无法在不使析构函数明确更改数据的情况下打印错误的东西。这是为什么?
您正在调用未定义的行为;这一切都可能发生!
为什么没有铿锵声警告?将ref返回到局部变量时会出现警告。
是否存在返回参数引用的有效(安全)用例?
<强>的std ::最大强>
假设您想要最多两个值:
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
的副本。我们不会使用x
y
与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
参数视为非空指针(就此问题而言,它与参考的距离非常接近)。
通常认为定义副本赋值运算符是idomatic。其中一个原因是因为自动生成的复制赋值运算符具有该签名:
N4618 12.8.2.2复制/移动赋值运算符[class.copy.assign]
类的隐式声明的复制赋值运算符的格式为
X& X::operator=(const X&)
将此作为警告会惩罚规范代码!至于为什么人们可能想做这样的事情(除了它是规范的),那是另一个话题......
答案 1 :(得分:1)
没有警告,因为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的潜在来源”并且它会检查每个调用。这不仅会导致额外的开销,而且有效地要求getref()
为inline
(由于编译器通常被设计为一次仅在单个翻译单元上运行,因此必须定义getref()
在每个模块中,它用于编译器保留此信息)。目前这是不可行的,但对于未来的编译器可能会变得更加实用(例如,如果跨模块优化成为标准)。getref()
rvalue,或者特意打算让getref()
成为潜在的UB来源,那么就不要发出警告。这是最可行的选择,因为它既不会损害合法代码,也不需要能够进行跨模块优化的编译器。因此,大多数编译器通常会选择不发出警告,假设程序员知道他们在做什么。即使指定了-Wall
,Clang,GCC,ICC和MSVC也不会发出任何警告。