由于右值到左值的转换而没有使用 forward 导致的错误
Error due to rvalue to lvalue conversion and not using forward
以下代码显示 windows 和 Linux 机器中的分段错误。 Parent
class 存储了一个指针,但是由于右值到左值的转换,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
内) .
我发现很难理解以下内容
为什么在 parent
class 中对 ptr
的引用变得无效?是因为 ptr
一旦在 MyFunction
内部使用就变成了局部变量,而 Child
构造函数仅获得对该局部变量的引用吗?
谢谢!
首先,这是正在发生的事情,请记住 getInt
returns 一个右值:当您 不 转发 ptr
时,你最终调用了 (post-reference collapsing)
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
..:[=36=] 稍微复杂一些
碰巧转发 ptr
解决了这里的问题,因为在您的示例中您将右值传递给 MyFunction
;但是您当然应该能够使用左值调用 MyFunction
并使其正常工作,而 forward
本身并不能解决这个问题。 实际 问题是以引用类型开始实例化 Parent
;在 C++11 中,这通常通过应用 std::decay
.
来避免
一旦这个问题得到解决,第二个问题就会变得明显(由于引用折叠规则,在你当前的代码中幸运地掩盖了):父构造函数中的 PointerType&&
不是转发引用,因为 PointerType
是类型的参数,不是构造函数。结果,当传入一个左值时,调用的构造函数最终将成为 Parent<int*>::Parent(int*&&)
,这将无法编译。不幸的是,this 的 'proper' 解决方案在 C++11 中很复杂……简短的总结是,如果你想将完美转发应用于 Parent
' s 构造函数,该构造函数需要成为模板;但是 正确地 实施这个在旧标准中是复杂和冗长的,所以我将研究它作为 reader 的练习并在这里选择简单的解决方案:按值接受和搬家。
这两个都固定后,结果如下:
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)
))
{}
};
(但是有人想知道,'pointer types' 一开始真的保证完美转发吗?)
以下代码显示 windows 和 Linux 机器中的分段错误。 Parent
class 存储了一个指针,但是由于右值到左值的转换,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
内) .
我发现很难理解以下内容
为什么在 parent
class 中对 ptr
的引用变得无效?是因为 ptr
一旦在 MyFunction
内部使用就变成了局部变量,而 Child
构造函数仅获得对该局部变量的引用吗?
谢谢!
首先,这是正在发生的事情,请记住 getInt
returns 一个右值:当您 不 转发 ptr
时,你最终调用了 (post-reference collapsing)
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
..:[=36=] 稍微复杂一些
碰巧转发 ptr
解决了这里的问题,因为在您的示例中您将右值传递给 MyFunction
;但是您当然应该能够使用左值调用 MyFunction
并使其正常工作,而 forward
本身并不能解决这个问题。 实际 问题是以引用类型开始实例化 Parent
;在 C++11 中,这通常通过应用 std::decay
.
一旦这个问题得到解决,第二个问题就会变得明显(由于引用折叠规则,在你当前的代码中幸运地掩盖了):父构造函数中的 PointerType&&
不是转发引用,因为 PointerType
是类型的参数,不是构造函数。结果,当传入一个左值时,调用的构造函数最终将成为 Parent<int*>::Parent(int*&&)
,这将无法编译。不幸的是,this 的 'proper' 解决方案在 C++11 中很复杂……简短的总结是,如果你想将完美转发应用于 Parent
' s 构造函数,该构造函数需要成为模板;但是 正确地 实施这个在旧标准中是复杂和冗长的,所以我将研究它作为 reader 的练习并在这里选择简单的解决方案:按值接受和搬家。
这两个都固定后,结果如下:
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)
))
{}
};
(但是有人想知道,'pointer types' 一开始真的保证完美转发吗?)