在 const ref 类型参数上使用临时对象时,编译器是否应该警告不安全行为?
Should compiler warn about unsafe behaviour when using temporary object on const ref type parameters?
让我们考虑以下不正确的代码:
#include <string>
#include <iostream>
struct C {
C(const std::string &_s): s(_s) { }
void run() {
std::cout << "Here we are using the string " << s << "\n";
}
const std::string &s;
};
int main() {
C a("/tmp/example.log");
a.run();
return 0;
}
无论是 g++ 6.3.0 还是 clang 3.8.1 都会在没有警告的情况下编译这段代码,即使使用 -W -Wall -Werror
,但即使对于粗心的程序员来说,行为也只是说明这里有问题:
Here we are using the string Here we are usin
为什么结果并不像人们天真地期望的那样?从 const char *
创建的 std::string
对象的生命周期仅限于构造函数的范围。因此,当 foo()
构造函数 returns 时, foo::s
字段引用了不存在的对象。轰隆隆。
如果有人理解这个想法,就很容易介绍修复方法。它可以是显式创建 std::string
对象并将其作为参数传递:
std::string s("/tmp/example.log");
C a(s);
另一个我认为更安全的方法是将 foo::s
字段声明更改为:
const std::string s;
制作参数的本地不可修改副本,但它增加了复制对象的成本。好吧,在这个例子中,复制 std::string 不会自动导致进行深度复制,但对于自己实现的 class 它可能是完全不同的事情。
对于 C++11,几乎没有其他方法可用,如 中所述,但是 - 例如 - 在嵌入式 Linux 环境中,当你坚持使用旧编译器和一些遗留代码时这不一定是一个不错的选择。
但是,无论程序员选择何种解决方案,都必须知道存在问题。即使我正确地编写了我的代码,我也愚蠢地认为如果其他开发人员掉入传递 const char *
参数的陷阱,编译器会足够聪明地检测出明显危险的情况。然而,我决定编写一些代码来检查编译器的行为,但我很失望——人们很容易陷入引用已经不存在的对象的陷阱。
将临时对象作为 const 引用传递是一种公认的危险,它已在 C++11 中以某种方式解决,但仍将责任转嫁给程序员以积极抵制此类行为。那么,期望编译器发出警告是否合理,或者应该依赖外部静态分析工具?
根据设计将临时绑定到 const 参考作品,不应发出任何警告。它不是您代码中问题的根源。问题是您使用 const 引用作为成员而不是管理它的正确性。即使你按照你的建议传递 std::string
,也不会消除问题 - 该字符串必须比你的对象长寿才能使你的程序正确。并且将成员创建为 std::string
或 const std::string
不仅更安全,而且在这种情况下是安全且正确的方法。在某些情况下,您可能希望将 const 或非 const 引用存储为成员,但您必须了解自己在做什么并确保该引用不会悬空。在这种情况下,编译器不太可能检测到极端情况并向您发出警告。
IMO 的问题不是将临时对象作为 const ref 传递,而是按原样存储。将成员更改为 const std::string s;
(甚至非常量)将解决问题。
如果您打算将其存储为引用,则必须保证对象在外部的生命周期。事实上,如果不够小心,在 C++ 中很容易射中自己的腿。
而且我真的不希望编译器在将输入参数存储为引用时发出警告,即使不小心可能会很危险,因为存在有效的用例(即所有情况参数生命周期超过了对象的生命周期,我不想在内部复制它)。
请注意,这同样适用于存储为指针等。
此外,通过值传递参数(与 const ref 相反)根本不能解决问题,因为这样的参数作为值复制仍然是临时的,所以当绑定到引用时,它也会离开构造函数时离开。
让我们考虑以下不正确的代码:
#include <string>
#include <iostream>
struct C {
C(const std::string &_s): s(_s) { }
void run() {
std::cout << "Here we are using the string " << s << "\n";
}
const std::string &s;
};
int main() {
C a("/tmp/example.log");
a.run();
return 0;
}
无论是 g++ 6.3.0 还是 clang 3.8.1 都会在没有警告的情况下编译这段代码,即使使用 -W -Wall -Werror
,但即使对于粗心的程序员来说,行为也只是说明这里有问题:
Here we are using the string Here we are usin
为什么结果并不像人们天真地期望的那样?从 const char *
创建的 std::string
对象的生命周期仅限于构造函数的范围。因此,当 foo()
构造函数 returns 时, foo::s
字段引用了不存在的对象。轰隆隆。
如果有人理解这个想法,就很容易介绍修复方法。它可以是显式创建 std::string
对象并将其作为参数传递:
std::string s("/tmp/example.log");
C a(s);
另一个我认为更安全的方法是将 foo::s
字段声明更改为:
const std::string s;
制作参数的本地不可修改副本,但它增加了复制对象的成本。好吧,在这个例子中,复制 std::string 不会自动导致进行深度复制,但对于自己实现的 class 它可能是完全不同的事情。
对于 C++11,几乎没有其他方法可用,如
但是,无论程序员选择何种解决方案,都必须知道存在问题。即使我正确地编写了我的代码,我也愚蠢地认为如果其他开发人员掉入传递 const char *
参数的陷阱,编译器会足够聪明地检测出明显危险的情况。然而,我决定编写一些代码来检查编译器的行为,但我很失望——人们很容易陷入引用已经不存在的对象的陷阱。
将临时对象作为 const 引用传递是一种公认的危险,它已在 C++11 中以某种方式解决,但仍将责任转嫁给程序员以积极抵制此类行为。那么,期望编译器发出警告是否合理,或者应该依赖外部静态分析工具?
根据设计将临时绑定到 const 参考作品,不应发出任何警告。它不是您代码中问题的根源。问题是您使用 const 引用作为成员而不是管理它的正确性。即使你按照你的建议传递 std::string
,也不会消除问题 - 该字符串必须比你的对象长寿才能使你的程序正确。并且将成员创建为 std::string
或 const std::string
不仅更安全,而且在这种情况下是安全且正确的方法。在某些情况下,您可能希望将 const 或非 const 引用存储为成员,但您必须了解自己在做什么并确保该引用不会悬空。在这种情况下,编译器不太可能检测到极端情况并向您发出警告。
IMO 的问题不是将临时对象作为 const ref 传递,而是按原样存储。将成员更改为 const std::string s;
(甚至非常量)将解决问题。
如果您打算将其存储为引用,则必须保证对象在外部的生命周期。事实上,如果不够小心,在 C++ 中很容易射中自己的腿。
而且我真的不希望编译器在将输入参数存储为引用时发出警告,即使不小心可能会很危险,因为存在有效的用例(即所有情况参数生命周期超过了对象的生命周期,我不想在内部复制它)。
请注意,这同样适用于存储为指针等。
此外,通过值传递参数(与 const ref 相反)根本不能解决问题,因为这样的参数作为值复制仍然是临时的,所以当绑定到引用时,它也会离开构造函数时离开。