我发现了一些非常奇怪的东西,请查看以下代码:
#include <cstring>
#include <cstdio>
#include <utility>
#include <vector>
using namespace std;
class A {
public:
int n = 0;
A(const char* p) { n = strlen(p); };
A(A const&) = delete;
void operator=(A const&) = delete;
};
void F(vector<pair<const char*, const A&>> v) {
printf("F\n");
for(vector<pair<const char*, const A&>>::iterator it = v.begin();it!=v.end();++it) printf(" '%s': %p %i\n", it->first, &it->second, it->second.n);
};
int main(int, char**) {
F({
{ "A", "A" },
{ "B", "BB" },
{ "C", "CCC" },
{ "D", "DDDD" }
});
};
现在使用clang++ -std=c++11 -Wall -Wextra -Wpedantic -O0 main.cc -o main
或类似的东西(禁用优化)进行编译。
你应该看到这样的输出:
F
'A': 0x7fff57a0b988 1
'B': 0x7fff57a0b9d0 2
'C': 0x7fff57a0ba18 3
'D': 0x7fff57a0ba60 4
这很好,编译器会自动创建矢量对象和相应的A&
引用,这些引用都是不同的。
现在,使用clang++ -std=c++11 -Wall -Wextra -Wpedantic -O1 main.cc -o main
进行编译,注意我刚刚添加了最低级别的优化。
你会看到,
F
'A': 0x7fff5ac54b30 1629262454
'B': 0x7fff5ac54b30 1629262454
'C': 0x7fff5ac54b30 1629262454
'D': 0x7fff5ac54b30 1629262454
所有参数都引用相同的A&
对象,我发现错误。
以下是我的编译器详细信息:
Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix
这是预期的行为吗?这是编译器错误吗?
更新:
正如Matt McNabb所指出的那样,vectors和initializer_lists并不是设计用于const引用(尽管它在clang中编译得很好)。但是,在编写与void F(vector<pair<char*, A&&>>) {}
相同的功能时,错误仍然存在。
答案 0 :(得分:4)
您的代码似乎有点奇怪。你这样做了:
void F(vector<pair<const char*, const A&>> v) {
所以你期待一个引用A
个对象的向量。但是你没有任何A物体。你传递的是字符串文字,编译器会隐式创建A对象 - 但这些是临时的,所以当你的函数体运行时,它们已经消失了,引用它们是未定义的行为,这就是为什么它适用 - O0,但不是-O1。
如果要隐式创建A
个对象然后保留它们,则不能使用引用。尝试
void F(vector<pair<const char*, const A>> v) {
答案 1 :(得分:2)
这是由于pair
的构造函数需要转发引用:
template<class U, class V> pair (U&& a, V&& b);
创建传递给向量构造函数的initializer_list时,会创建临时对。每对使用提供的初始化器构建。例如,当您使用
构造第一对时{ "A", "A" }
编译器发现最佳匹配构造函数是template <typename U,typename V> pair(U&&,V&&)
,其中U和V为
const char (&)[2]
。这意味着在呼叫站点上不会创建临时A.临时A在对构造函数内部创建:
template<class U, class V> pair (U&& a, V&& b)
: first(std::forward<U>(a)),
second(std::forward<V>(b))
// second is of type const A&, but std::forward<V>(b) is an rvalue
// of type const char [2]
{
}
由于参数类型(const char (&)[2])
与成员类型(const A &
)不匹配,因此创建临时表来初始化成员,但此临时表只会持续到构造函数退出。意味着你的一对留下悬挂的参考。
避免这种情况的一种方法是在呼叫站点明确创建临时值:
F({
{ "A", A("A") },
{ "B", A("BB") },
{ "C", A("CCC") },
{ "D", A("DDDD") }
});
现在临时值将持续到F
返回,并且由于类型匹配,因此pair
构造函数内不会创建临时值。
请注意,如果你有std::pair
的C ++ 03格式,它没有转发引用,那么行为就是你所期望的。临时值将在main中创建,并持续到F()
的调用返回。
答案 2 :(得分:1)
他们没有引用相同的A&
对象。它们引用碰巧具有相同内存地址的不同对象,因为它们的生命周期不会重叠。
对于生命周期不重叠的对象是否存在或不占用相同的内存地址,您没有特别的期望。