【问题标题】:Inheritance References not working correctly继承引用无法正常工作
【发布时间】:2025-12-27 21:10:06
【问题描述】:

当运行以下代码时,涉及通过一个名为 B 的类的方法更改 int 的值,该类继承自模板类 A,int 的值没有改变,我不明白为什么,我已经用 clang trunk 和 gcc trunk 测试了这两个:

#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 无法绑定到左值引用。

所以我的问题是为什么 B 的构造函数选择 Args = [int] 而不是 Args = [int&] 以及当它将该 Args 传递给接受 T& 的构造函数时它如何做到这一点?

【问题讨论】:

  • 模板参数推导类似于通过检查羊内脏来预测未来。

标签: c++ templates inheritance


【解决方案1】:

模板类型参数被推断为引用类型的唯一情况是当您将相应的函数参数声明为 T&amp;&amp; 并将左值传递给函数时。

这意味着在这种情况下Args 被推断为{int}B的构造函数实例化后是这样的:

B(int arg) : A<int>(arg) {}

这是完全有效的,因为arg 是一个左值,所以A 的构造函数的a_num_ 参数可以绑定到它。然后将该引用复制到Aa_num 成员,然后它绑定的函数参数超出范围,a_num 成为悬空引用。因此,当您尝试通过 a_num 分配时的行为是未定义的。


如果您希望B 的构造函数通过引用获取其参数,那么您需要将参数的类型定义为引用:

tmeplate <typename... Args>
B(Args&... args) : A<int>(args) {}

如果你这样做,那么b.a_num 将引用你在main 中定义的num,一切都会如你所愿。

【讨论】:

  • 当你说B(int arg) : A&lt;int&gt;(arg) {} 有效时,当我在编译器上尝试它时,它会给出以下警告:warning: binding reference member 'a_num' to stack allocated parameter 'a_num_' 等因为它发出警告,它不应该意识到使用 { int&} 比使用 {int} 更正确?
  • 其实我很抱歉,忽略那条评论,它实际上并没有给出警告,我正在测试错误的东西,但它不应该检测到 arg 是堆栈分配的并给出警告吗?
【解决方案2】:

你应该更改 B 的构造函数以将 Args 作为引用 Args&amp;...

template<typename... Args>
B(Args&... args) : A<int>(args...) {}

现在您的代码将按照您最初的要求打印1634

确认内存地址

如果你想确认那些变量指向同一个内存地址,你可以这样做:

std::cout << &num << '\n';
std::cout << &b.a_num << '\n';

这会打印相同的内存地址。例如:

0x7fff5508cac8
0x7fff5508cac8

使用指针

既然想法是让numA::a_num 都指向同一个内存地址,那么值得一提的是使用指针的解决方案。在这种情况下,您不必更改 B 的构造函数,但必须将 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;
}

此代码还会按照您最初的要求打印1634

编辑:我应该记住,这段代码是不好的做法,不应该推荐用于生产环境。局部变量的内存地址永远不应被传递,因为它们仅在范围存在于堆栈中时才有效。我们可以通过使用智能指针或简单地复制值而不是重用它们的内存地址来实现更好的代码。

【讨论】:

  • 谢谢@MilesBudnek,你是对的。我已经简化了main() 函数,所以现在没有未初始化的指针。但另一方面,它现在将本地 num 变量的地址传递给构造函数,这在现实生活中将是一段疯狂的代码(从不推荐)。
  • 指针示例的“点”是什么?这比原始代码差。
  • @MM OP 试图使 numA::a_num 具有相同的内存地址,因此当您修改 A::a_num 字段时,您还修改了存在的 num 变量在main() 范围内。使用指针是另一种选择,所以我决定提一下。但我同意这样的代码纯粹是不好的做法。
  • 感谢您的回答,我已将其标记为已接受,尽管我想知道您是否也可以用 Args... 解释,因为没有这样做,所以无法推断参考(这就是说将Args... 重写为int) 会引起大多数编译器的警告:warning: binding reference member 'a_num' to stack allocated parameter 'a_num_',这只是轻微的忽略还是有理由这样做?
  • 由于Args... 没有明确声明为引用或指针,编译器只会尝试将其推断为按值传递的情况。换句话说,如果参数的 type 既不是指针也不是引用,则 param(在这种情况下为Args...)将是传递的任何内容的副本在。