struct X
{
X():mem(42){}
void f(int param = mem) //ERROR
{
//do something
}
private:
int mem;
};
任何人都可以给我一个理由,说明为什么这在C ++中是非法的?!也就是说,我知道这是一个错误,我知道错误意味着什么,我只是无法理解为什么这会是非法的!
答案 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->
限定,通常不能在呼叫站点进行评估。