c++ 理解标准对〈const T&,const U&>

hof1towb  于 2023-01-22  发布在  其他
关注(0)|答案(2)|浏览(160)

我试图创建一个类,它从两个成员变量示例化一个std::pair<const T&, const U&>。我开始这个过程是出于好奇,并且惊讶地看到创建多个Pair对象的结果,在下面的例子中。

struct Pair {
    int num;
    string s;
    pair<const int&, const string&> pr{make_pair(num, s)};

    Pair(int n, const string& s) : num(n), s(s) {}
};

ostream& operator<<(ostream& out, const Pair& p) {
    out  << setw(2) << p.num << ", "  << p.s << ": <" << p.pr.first << "," << p.pr.second << ">";
    return out;
}

/******************************************************************************/
int main() {

    cout << string(15, '-') << " PIECEMEAL " << string(15, '-') << endl;
    Pair p1{1, "normal1"};
    Pair p2{2, "normal2"};
    cout << p1 << endl;
    cout << p2 << endl << endl;

    cout << string(15, '-') << "  VECTOR " << string(15, '-') << endl;
    vector<Pair> v;
    for (int i=0; i< 05; ++i) {
        v.emplace_back(i, "from_vec'" + ::to_string(i) + "'");
    }

    for(const auto & p : v) {
        cout << p << endl;
    }
}

我原以为pair中的const引用类型声明会分别引用Pair成员nums。看起来pair中的引用最终引用了(在某种程度上)最近构造的PairPair成员。
下面是输出:

--------------- PIECEMEAL ---------------
 1, normal1: <2,normal2>
 2, normal2: <1586964272,normal2>

---------------  VECTOR ---------------
 0, from_vec'0': <4,from_vec'4'>
 1, from_vec'1': <1586964272,from_vec'4'>
 2, from_vec'2': <1586964272,from_vec'4'>
 3, from_vec'3': <1586964272,from_vec'4'>
 4, from_vec'4': <1586964272,from_vec'4'>

我查看了cppreference,试图了解发生了什么,但仍然没有领会到,为什么pair中的引用不再引用Pair成员nums

qv7cva1a

qv7cva1a1#

您的代码有两个问题。
第一:

pair<const int&, const string&> pr{make_pair(num, s)}

std::make_pair(num, s)首先创建一个临时pair<int, string>,然后用这个临时pair初始化pr
请注意,pair<int, string>pari<const int&, const string&>不同,这意味着它不会触发默认的移动构造函数。
相反,如果您查看cppreference,这将触发第6个重载,并且实际上用这个临时的pair来分配pr.firstpr.second,从而产生悬空引用。
要修复此问题,您应该使用以下命令专门创建pair引用:

pr{std::make_pair(std::ref(num), std::ref(s)}

或者直接使用nums创建pr

pr{num, s}

第二:

v.emplace_back(i, "from_vec'" + ::to_string(i) + "'");

这一行将触发默认的move构造函数,因为您没有自己指定一个,它将在Pair上执行成员级移动。
但是,当您将pr移动到新的Pair时会发生什么?旧的pr引用旧的num和旧的s。将旧的pr移动到新的Pair不会更改pr的值,因此它将继续引用旧的num和旧的s。从而产生悬空引用。
因此,您必须手动定义它们,而不是依赖默认生成的移动构造函数:

Pair(Pair&& pair) noexcept
: num(std::exchange(pair.num, {})
, s(std::move(pair.s))
{}

注意,你不需要在初始化器列表中构造pr,因为你已经有了pr的默认初始化器。
Demo

kfgdxczn

kfgdxczn2#

您无意中创建了一个对,它包含对另一个对的常量引用,而该对在堆栈上分配并立即释放。换句话说,这是未定义的行为。
下面是示例化Pair时所发生的事情:
1.调用Pair构造函数。
1.调用Pair初始化式。
1.初始化器调用make_pair,在堆栈上创建一对int和std::string。
1.这个临时对被赋值给pr,它里面的const引用现在引用临时对的内存。
1.初始化器退出,释放堆栈上的临时对。pr内部的int和string引用现在引用已被释放的内存。
此外,出于内存安全的考虑,你真的不应该把对其他成员的引用存储在你的结构体中,如果你想要一个std::对你的两个值,你应该创建一个成员函数来返回它,而不是把它存储在结构体本身中。

相关问题