为什么成员变量不能用作参数的默认值?

6
我来为您翻译关于it技术的内容:

可能是重复问题:
非静态成员函数的默认参数

如果我理解得不对,请纠正我,但我认为默认参数的工作方式如下:

当编译器看到函数调用时,它会开始将参数推入堆栈。当参数用完后,它会开始将默认参数推入堆栈,直到填满所有必需参数(我知道这是一种简化方式,因为实际上参数是从右到左推送的,所以它将从默认值开始,但思想是相同的)。

如果这是真的,为什么成员变量不能用作默认值呢?在我的看法中,由于编译器像往常一样在调用站点上推送它们,它应该能够很好地解析它们!

编辑 由于答案似乎误解了我的问题,让我澄清一下。我知道这是情况,并且我知道语言允许什么和不允许什么。我的问题是,既然它似乎自然而然地工作,为什么语言设计者选择不允许这样做。

4个回答

4
您所要求的实质可以归结为以下简单示例:
void foo(int a, int b = a);

在C ++中不允许这样做。C ++不允许默认参数依赖于其他参数。

使用类成员作为默认参数只是上述情况的一个特例,因为类成员是通过this指针访问的,而this指针只是每个非静态成员函数的另一个隐藏参数。

所以,问题实际上是为什么。

void foo(int a, int b = a);

不允许这样做。

禁止此操作的一个明显原因是,它会对参数的计算顺序施加额外要求。如您所知,在C++中,函数参数的计算顺序是未指定的 - 编译器可以以任何顺序计算参数。但是,为了支持上述默认参数功能,编译器必须确保在计算b之前先计算a。这感觉就像是一种过度的要求,限制了我们在C++中常见的自由计算顺序。

请注意,这个

int a;

void foo(int b = a);

在C++中允许使用该语法。显然,它不会出现前述的评估顺序问题。

在你的例子中,这确实可能是一个问题。但是当a只是一个地址时,强制使用&var就没有问题了,因为&var根本没有任何副作用。你甚至可以评估它两次(一次为a,一次为b),然后以任何顺序执行它。 - Baruch
@baruch: 我不确定你的意思。如果默认参数不依赖于其他参数的运行时属性,则通常允许语言使用。成员变量的地址取决于this的值,这与我的示例没有任何区别。 - AnT stands with Russia
我的意思是,由于*this只是指向用于实例化调用的实例的指针,因此它更像是调用bar(&c,&c + 4),这是完全合法的。编译器可以按任何顺序进行评估。第二个参数恰好取决于与第一个参数发送的相同值,而不是第一个参数本身。 - Baruch
2
@baruch:是的,但如果c不是预先准备好的变量,而是函数调用的结果,例如obtain_c_from_somewhere().bar()呢?两次调用该函数是不可接受的。在这种情况下,唯一的解决方案是始终确保首先调用obtain_c_from_somewhere()(以获取this),然后再准备其他参数。这正是我在我的答案中谈到的关于评估顺序的额外要求。 - AnT stands with Russia

1

1
是的,我知道规则。我正在寻找为什么。 - Baruch
@baruch 我更新了我的回答并解释了为什么这样做。 - user93353
名称查找发生在声明位置?为什么?在我的问题中已经解释过,对我来说似乎应该在调用位置进行查找。我猜这个问题已经变得太“为什么不按我的方式做”了。除了这样做的方法是这样的之外,可能没有明确的答案,因为我可能会在每个新的解释层次上继续问“为什么”。 - Baruch
@baruch - 详细说明一下 - 名称查找发生在编译时。在编译时,要使用的变量将绑定到函数调用。变量的值在运行时评估。更新了我的答案以反映这一点。 - user93353
1
抱歉,这没有意义。在调用站点,“this”是非常明确的。在 CPU 级别上,它只是另一个参数。要访问“this->member”,编译器只需要“offset_of(member)”即可。 - MSalters
显示剩余2条评论

1

我认为这些是标准中最合适的段落,特别是§9:

8.3.6 默认参数 [dcl.fct.default]

§7 不得在默认参数中使用局部变量

§9 [...] 同样,非静态成员不得在默认参数中使用,即使它没有被评估,除非它出现为类成员访问表达式(5.2.5)的id-expression或者除非它用于形成指向成员的指针(5.3.1)。


1
我知道这是不被允许的。我试图理解背后的逻辑,因为它似乎在刻意阻止它正常工作。 - Baruch

1
总结Nawaz在链接问题中的优秀回答:调用void Foo::Bar(int a = this->member)实际上意味着void Foo__Bar(Foo* this, int a = this->member)。显然,第二个参数在第一个参数之前无法计算,这违反了C++编译器可以按任何顺序评估参数的公理。

听起来是对的,所以我接受了。然而,它似乎还不完全正确。由于编译器在编译时具有this的地址,因此在调用c.Bar()时,它实际上将调用Foo:Bar(&c, &c+offset(member)),并且可以按任意顺序评估它们。 - Baruch
哦,显然可以为那个简单的情况使其工作。但是如果 c 不是一个指针,而是一个函数调用的返回值呢?你不想调用两次。 - MSalters
为什么第一个参数需要在第一个参数之前被评估,难道第二个参数不能像您在我的答案中建议的那样通过offsetof(member)进行访问吗? - user93353
@user93353:不,直到第一个参数被评估之前不会执行(因为它是相对于该参数的偏移量)。这意味着您已经规定了参数评估的顺序。 - MSalters
@MSalters 不需要。假设调用是c.Bar(),那么它可以被评估为c.(&c, offsetof(member)) - 它们完成的顺序并不重要。第一个参数是&c - 因此第二个参数可以使用它来评估。 - user93353
@user93353:在water函数中,第二个参数不是offset_of(member),而是*(member_type*)((char*)this + offsetof(member))。请记住,这只是一个默认值,调用者可能会提供另一个具有相同类型的表达式。被调用方必须处理这两种情况。 - MSalters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接