非静态成员作为非静态成员函数的默认参数

时间:2010-12-27 14:42:24

标签: c++ default-value member-functions

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

任何人都可以给我一个理由,说明为什么这在C ++中是非法的?!也就是说,我知道这是一个错误,我知道错误意味着什么,我只是无法理解为什么这会是非法的!

9 个答案:

答案 0 :(得分:38)

您的代码(简化):

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?

您的答案可能是100,因为在f() x1的{​​{1}}上调用了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明确地说,

  

每个都会评估默认参数   调用函数的时间。 订单   函数参数的评估是   未指定的因此,参数   不得使用函数   默认参数表达式,即使   他们没有被评估。

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


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

答案 1 :(得分:6)

必须在编译时知道默认参数。当你谈到像函数调用这样的东西时,那么函数在编译时是已知的,即使返回值不是,所以编译器可以生成该代码,但是当你默认为成员变量时,编译器不会知道在编译时在哪里找到该实例,这意味着它实际上必须传递一个参数(this)才能找到mem。请注意,您不能执行类似void func(int i, int f = g(i));的操作,这两者实际上是相同的限制。

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

答案 2 :(得分:5)

正如DeadMG上面提到的那样,有点像

void func(int i, int f = g(i))
出于同样的原因,

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

答案 3 :(得分: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值所必需的对象的语法,因此实际上不可能使用非静态成员变量作为默认参数的初始化器。

答案 4 :(得分:1)

出于一个原因,因为f是公开的,但mem是私有的。因此,代码如下:

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

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

除此之外,它会(或者至少可以)使代码生成变得有点棘手。通常,如果编译器要使用默认参数,它将获得它将作为函数声明的一部分传递的值。生成代码以将该值作为参数传递是微不足道的。当你可能传递一个对象的一个​​成员(可能是任意深入嵌套)然后添加一些东西,比如它可能是一个模板中的一个依赖名称,这可能(例如)命名另一个对象转换为正确的目标类型,你有一个使代码生成非常困难的方法。我不确定,但我怀疑有人想到这样的事情,并且认为最好保持保守,如果发现有充分的理由,可能稍后打开。 。鉴于我看到它出现问题的次数,我猜它会长久保持原样,只是因为它很少会引起问题。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

在没有默认参数的其他语言中使用(例如C#pre 4.0)

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

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

答案 7 :(得分:0)

ISO C ++第8.3.6 / 9节

  

非静态成员不得在默认参数表达式中使用,即使它也是如此   除非它作为类成员访问表达式(5.2.5)的id-expression出现,或者除非用于形成指向成员的指针(5.3.1),否则不进行求值。

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

答案 8 :(得分:0)

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

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

  • 无法使用具有非静态生命周期的变量,因为它们在通话时可能不存在。
  • 不能使用非静态成员变量,因为它们需要(隐式)this->限定,通常不能在呼叫站点进行评估。