C ++包含引用的对向量的奇怪行为

时间:2015-07-23 23:10:19

标签: c++ c++11 language-lawyer clang++

我发现了一些非常奇怪的东西,请查看以下代码:

#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&&>>) {}相同的功能时,错误仍然存​​在。

3 个答案:

答案 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) {

此处示例:http://ideone.com/VNIgal

答案 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&对象。它们引用碰巧具有相同内存地址的不同对象,因为它们的生命周期不会重叠。

对于生命周期不重叠的对象是否存在或不占用相同的内存地址,您没有特别的期望。