微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

由于右值到左值的转换以及未使用前向

如何解决由于右值到左值的转换以及未使用前向

以下代码显示了 Windows 和 Linux 机器中的分段错误Parent 类存储一个指针,但由于右值到左值的转换,Parent 最终存储了对指针的引用。 (这是我的理解,否则请更正)

#include <iostream>
#include <vector>

class Root {
public:
    virtual void Print() = 0;
};

template<typename PointerType>
class Parent : public Root {
public:
    Parent(PointerType&& p): ptr(p) {}

    void Print() override {
        std::cout <<"About to deref ptr" << std::endl;
        std::cout << *ptr << std::endl;
    }

    PointerType ptr;
};

class Child {
public:
    Root * root;
    template<typename PointerType>
    Child(PointerType&& p) {
        root = new Parent<PointerType>(std::forward<PointerType>(p));
    }
};



std::vector<Child> v;

template<typename PointerType>
void MyFunction(PointerType&& ptr) {  

    Child ch(ptr); /// ptr is lvalue here

    /// FIX : Child ch(std::forward<PointerType>(ptr));

    v.push_back(std::move(ch));
}

int* getInt() {
    return new int(10);
}

int main() {
    MyFunction(getInt()); /// pass any rvalue pointer /// it Could be "this" pointer also
    v[0].root->Print();
}

我说服自己,我总是需要在使用 std::forward 时使用 universal references(在函数 MyFunction 内部)。

我发现很难理解以下内容

为什么在ptr 类中对 parent 的引用变得无效?是不是因为 ptr 一旦在 MyFunction 中使用就变成了局部变量,而 Child 构造函数只获得了对这个局部的引用?

谢谢!

解决方法

首先,这是发生的事情,请记住 getInt 返回一个右值:当您转发 ptr 时,您最终会调用(引用后折叠)

  • MyFunction<int*>(int*&&)
  • 调用 Child::Child<int*&>(int*&)
  • 构造和存储 Parent<int*&>

当您执行转发ptr时,您最终会调用

  • MyFunction<int*>(int*&&)
  • 调用 Child::Child<int*>(int*&&)
  • 构造和存储 Parent<int*>

所以问题的表现是一个常见的悬空引用:Parent<int*&>::ptr 必然是一个 int*&,它恰好绑定到 getInt 返回的临时对象。然而,解决方案比仅仅教条地应用 std::forward..:

要复杂一些

碰巧转发 ptr 解决了这里的问题,因为您在示例中将右值传递给 MyFunction;但是您当然应该能够使用左值调用 MyFunction 并使其正常工作,而单独使用 forward 并不能解决这个问题。 实际问题是用引用类型开始实例化 Parent;在 C++11 中,这通常通过应用 std::decay 来避免。

一旦解决了这个问题,第二个问题就会变得明显(由于引用折叠规则,您当前代码中的运气掩盖了):父构造函数中的 PointerType&& 不是转发引用,因为 PointerType是类型的参数,而不是构造函数。因此,当传入左值时,调用的构造函数最终将是 Parent<int*>::Parent(int*&&),而后者将无法编译。不幸的是,this 的“正确”解决方案在 C++11 中很复杂……简短的总结是,如果您希望完美转发应用于 Parent 的构造函数,则该构造函数需要成为模板;但是在旧标准中正确实施是复杂和冗长的,所以我将把研究它作为读者的练习,并在此处选择简单的解决方案:按价值接受并移动。

这两个都修复了,结果看起来像:

template<typename PointerType>
class Parent : public Root {
public:
    Parent(PointerType p): ptr(std::move(p)) {}

    void Print() override {
        std::cout << "About to deref ptr\n" << *ptr << '\n';
    }

    PointerType ptr;
};

class Child {
public:
    Root* root;

    template<typename PointerType>
    Child(PointerType&& p):
        root(new Parent<typename std::decay<PointerType>::type>(
            std::forward<PointerType>(p)
        ))
    {}
};

Online Demo

(但是人们不得不怀疑,“指针类型”真的保证完美转发吗?)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。