35
struct X
{
   X():mem(42){}
   void f(int param = mem) //ERROR
   {
      //do something
   }
private: 
   int mem;
};

谁能给我一个理由来解释为什么这在 C++ 中是非法的?!也就是说,我知道这是一个错误,我知道这个错误是什么意思,我就是不明白为什么这会是非法的!

4

9 回答 9

47

您的代码(简化):

struct X
{
   int mem;
   void f(int param = mem); //ERROR
};

您想使用非静态成员数据作为成员函数参数的默认值。想到的第一个问题是:默认值属于哪个类的具体实例?mem

X x1 = {100};  //mem = 100
X x2 = {200};  //mem = 200

x1.f(); //param is 100 or 200? or something else?

您的答案可能100f()x1 具有mem = 100. 如果是这样,那么它需要实现实现f()为:

void f(X* this, int param = this->mem);

这反过来又要求在初始化其他参数之前首先初始化第一个参数。但是 C++ 标准没有指定函数参数的任何初始化顺序。因此这是不允许的。出于同样的原因,C++ 标准甚至不允许这样做:

int f(int a, int b = a); //§8.3.6/9

事实上,§8.3.6/9 明确表示,

每次调用函数时都会评估默认参数。函数参数的求值顺序未指定因此,函数的参数不应在默认参数表达式中使用,即使它们没有被评估。

本节的其余部分是一个有趣的阅读。


一个与“默认”参数相关的有趣主题(尽管与此主题无关​​):

于 2011-06-15T20:07:14.657 回答
7

默认参数必须在编译时知道。当您谈论诸如函数调用之类的事情时,即使返回值不是,该函数在编译时也是已知的,因此编译器可以生成该代码,但是当您默认使用成员变量时,编译器不会不知道在编译时在哪里可以找到该实例,这意味着它实际上必须传递一个参数 ( this) 才能找到 mem。请注意,您不能做类似void func(int i, int f = g(i));的事情,并且两者实际上是相同的限制。

我也认为这个限制是愚蠢的。但是,C++ 充满了愚蠢的限制。

于 2010-12-27T15:05:21.607 回答
5

正如 DeadMG 上面提到的,类似

无效函数(int i,int f = g(i))

出于同样的原因是非法的。然而,我认为这不仅仅是一个愚蠢的限制。为了允许这样的构造,我们需要限制函数参数的评估顺序(因为我们需要在 this->mem 之前计算它),但是 c++ 标准明确拒绝对评估顺序的任何假设。

于 2010-12-27T16:45:28.170 回答
2

重复问题中公认的答案是为什么,但标准也明确说明了为什么会这样:

8.3.6/9:

" 示例:以下示例中 X::mem1() 的声明格式不正确,因为没有为用作初始值设定项的非静态成员 X::a 提供对象。

int b;
class X
  int a;
  int mem1(int i = a);    // error: nonstatic member a
                          // used as default argument
  int mem2(int i = b);    // OK: use X::b
  static int b;
};

然而,X::mem2() 的声明是有意义的,因为不需要任何对象来访问静态成员 X::b。第 9 节中描述了类、对象和成员。”

...并且由于不存在提供解析该值所需的对象的语法X::a,因此实际上不可能使用非静态成员变量作为默认参数的初始值设定项。

于 2010-12-27T18:25:51.483 回答
1

ISO C++ 第 8.3.6/9 节

一个非静态成员不得在默认参数表达式中使用,即使它没有被评估,除非它作为类成员访问表达式 (5.2.5) 的 id 表达式出现,或者除非它用于形成指向成员的指针(5.3.1)。

另请查看该部分中给出的示例。

于 2010-12-27T14:50:18.700 回答
1

原因之一,因为f是公开的,但又mem是私有的。因此,像这样的代码:

int main() { 
    X x;
    x.f();
    return 0;
}

...将涉及外部代码检索 X 的私人数据。

除此之外,它还会(或至少可以)使代码生成变得有点棘手。通常,如果编译器要使用默认参数,它会获取它将作为函数声明的一部分传递的值。生成代码以将该值作为参数传递是微不足道的。当您可能传递对象的成员(可能嵌套任意深度)然后添加诸如它可能是模板中的从属名称的可能性时,这可能(例如)命名另一个对象并转换为正确的目标类型,你就有了一个让代码生成变得相当困难的秘诀。我不确定,但我怀疑有人考虑过这样的事情,并决定最好保持保守,并且可能如果找到一个好的理由,稍后再打开。鉴于我看到它出现问题的次数,我猜它会长时间保持原样,仅仅是因为它很少引起问题。

于 2010-12-27T15:00:01.157 回答
1

编译器必须知道地址以在编译时维护默认值。非静态成员变量的地址在编译时是未知的。

于 2010-12-27T15:00:10.473 回答
1

由于所有其他答案都只是讨论这个问题,我想我会发布一个解决方案。

与其他没有默认参数的语言一样(例如 C# pre 4.0)

只需使用重载即可提供相同的结果:

struct X
{
   X():mem(42){}
   void f(int param)
   {
      //do something
   }
   void f()
   {
      f(mem);
   }
private: 
   int mem;
};
于 2012-01-09T01:54:23.520 回答
0

默认参数在两个不同的步骤中,在不同的上下文中进行评估。
首先,默认参数的名称查找是在声明的上下文中执行的。
其次,默认参数的评估是在实际函数调用的上下文中执行的。

为了防止实现变得过于复杂,对可用作默认参数的表达式应用了一些限制。

  • 不能使用具有非静态生命周期的变量,因为它们在调用时可能不存在。
  • 不能使用非静态成员变量,因为它们需要(隐式)this->限定,通常无法在调用站点进行评估。
于 2010-12-27T17:40:15.280 回答