运行以下代码时,这涉及通过称为B的类的方法更改int的值,该类从模板化类A继承,int的值不变,而且我不明白为什么,我已经用clang干线和gcc干线测试了这两者:
#include <iostream>
template<typename T>
struct A
{
A(T& a_num_) : a_num(a_num_) {}
T& a_num;
};
struct B : public A<int>
{
template<typename... Args>
B(Args... args) : A<int>(args...) {}
void do_something()
{
a_num = 1634;
}
};
int main(void)
{
int num = 4;
B b {num};
b.do_something();
std::cout << num;
return 0;
}
我希望能打印1634张,而不是4张。
我将错误的范围缩小到B的构造函数,因为如果我将B的构造函数更改为:
B(int& x) : A<int>(x) {}
然后显示正确的值,但是当前的构造函数也应该接收一个int,因为当我键入以下内容时:
B b {num};
然后,它应该选择Args = [int&],因为这是满足A的构造函数(采用T&的唯一方法),所以这是怎么回事?在这种情况下,a_num是什么?只是一个垃圾引用,什么都不引用,或者可能是一个临时对象?
我还试图将do_something函数重写为
A<int>::a_num = 1634
但是它仍然无法更改它。
我还注意到将b声明为:
B b {6};
虽然没有办法将PR值6绑定到l值引用,但也可以使用。
所以我的问题是,为什么B的构造函数为什么选择Args = [int]而不是Args = [int&],当将Args传递给采用T&的构造函数时,如何做到这一点?
答案 0 :(得分:4)
只有当您将相应的函数参数声明为T&&
并将左值传递给函数时,才能将模板类型参数推导出为引用类型。
这意味着在这种情况下,Args
被推导为{int}
。实例化B
的构造函数后,它看起来像这样:
B(int arg) : A<int>(arg) {}
这是完全正确的,因为arg
是一个左值,因此a_num_
的构造函数的A
参数可以绑定到它。然后,您将该引用复制到A
的{{1}}成员,然后将其绑定到的函数参数超出范围,并且a_num
变成一个悬空引用。因此,当您尝试通过a_num
进行分配时的行为是不确定的。
如果您希望a_num
的构造函数通过引用接受其参数,则需要将参数的类型定义为引用:
B
如果您这样做,那么tmeplate <typename... Args>
B(Args&... args) : A<int>(args) {}
将是对您在b.a_num
中定义的num
的引用,一切都会按预期运行。
答案 1 :(得分:2)
您应更改B的构造函数,以oleg/feature/foo
作为参考Args
:
Args&...
现在,您的代码将按您的原意打印template<typename... Args>
B(Args&... args) : A<int>(args...) {}
。
如果要确认这些变量指向相同的内存地址,可以执行以下操作:
1634
这将打印相同的内存地址。例如:
std::cout << &num << '\n';
std::cout << &b.a_num << '\n';
由于想法是使0x7fff5508cac8
0x7fff5508cac8
和num
指向相同的内存地址,因此值得一提的是使用指针的解决方案。在这种情况下,您不必更改B的构造函数,而必须将A::a_num
更改为A::a_num
。
T*
此代码还会按照您的原始要求打印template<typename T>
struct A
{
A(T* a_num_) : a_num(a_num_) {}
T* a_num;
};
struct B : public A<int>
{
template<typename... Args>
B(Args... args) : A<int>(args...) {}
void do_something() {
*a_num = 1634;
}
};
int main(void)
{
int num = 4;
B b {&num};
b.do_something();
std::cout << num;
}
。
编辑:我应该记住此代码是不好的做法,绝不建议将其用于生产。局部变量的内存地址绝对不能传递,因为它们仅在范围存在于堆栈中时才有效。我们可以使用智能指针或简单地通过复制值而不是重新使用其内存地址来实现更好的代码。