如何在C++中将成员变量用作默认参数?

60

我想让一个成员函数的参数变为可选项。当没有提供参数时,它将使用一个成员变量。

然而,当我尝试编译它时,它显示:

error: invalid use of non-static data member 'Object::initPos'

为了隔离问题,我尝试将int类型设为默认值,编译通过了。 我想知道我的代码出了什么问题,以及如何使用成员函数作为默认值。

谢谢您的帮助!

Object.h

class Object
{
    public:
       ...
       void MoveTo(double speed, Point position);

    protected:
       Point initPos; 
       Point currPos;

};

Object.c

void Object::MoveTo(double speed, Point position = initPos)
{
    currPos = postion;
}

Point.h

class Point
{
    ...

    private:
       double x;
       double y;
       double z; 
};

1
你的问题让我想到了这个:https://dev59.com/5GDVa4cB1Zd3GeqPdXCU - Emile Cormier
4个回答

75
成员函数的默认参数表达式只能依赖于类或全局范围内的内容。默认参数也必须在方法的声明中指定(即在头文件中)。为了解决这个问题,您需要使用两个重载的MoveTo方法。一个接受一个参数,另一个接受两个参数。接受1个参数的方法调用另一个方法,并传递您认为是默认值的值。
void Object::MoveTo(double speed)
{
    MoveTo(speed, initPos);
}

void Object::MoveTo(double speed, Point position)
{
    // Everything is done here.
}

注意,当您调用MoveTo(double)时,调用MoveTo(double, Point)可以让您仅编写一次MoveTo的实现,从而遵守DRY原则。

2
谢谢!我知道我可以这样做。我只是想知道是否有更短的方法来实现,比如将其作为默认参数。但我想这可能是唯一的方法。 - tuzzer
那么,如果我不只有一个可选参数,而是有很多呢? 比如说: void Object::MoveTo(double speed, Point position = initPos, Point a = m_a, Point b = m_b, Point c = m_c) 那我难道要写很多个函数吗? - tuzzer
1
@MatthewChan:不幸的是,是的。也许你可以改变设计,让用户传递一个对象代替传递许多参数。这个对象已经包含了合理的默认值,用户只需更改需要不同的值即可。如果您决定以这种方式实现,您可能会对方法链习惯用法感兴趣,以使设置多个参数更加简洁(http://en.wikipedia.org/wiki/Method_chaining)。 - Emile Cormier
1
只是关于方法链的一个问题... 如果在C++中,你会这样做"return *this"对吧?那么这不就是简单地复制对象的实例吗?那么该实例实际上不会改变,对吧?比如我有一个叫做Car的对象,并且我有一个名为myCar的Car实例。myCar.setColour(RED).setBrand(PORCHE).setYear(2012) - tuzzer
*this 是对当前实例的引用。如果您的setter签名类似于 Car& setColor(Color c)(注意和号),那么它将返回对汽车的引用,而不是副本。另一方面,如果签名为 Car setColor(Color c)(注意缺少和号),则会返回汽车的副本。为了使方法链接工作,因此需要返回对象的引用。 - Emile Cormier
更多的方法链接信息请参考:http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.20。他们称之为命名参数惯用法。 - Emile Cormier

19

默认值不是原型的一部分,即它们由调用者解析,而不是由函数本身解析。因此,首先,它们必须对调用者可见。其次,它们无法访问类的受保护成员。(我相当确定您甚至不能使用公共成员作为默认值,但我太累了,无法检查。)

为了解决这个问题,可以像其他答案建议的那样使用链接重载。


谢谢。我尝试将initPos更改为public,但正如你所说,它仍然不起作用。 那么我们只能使用常量作为默认参数吗(例如非成员函数,可以直接输入的内容)? - tuzzer
4
它们由调用者解决,而不是函数本身解决:我明白了,那就是原因。 - Arkajyoti Banerjee
1
这个答案比被采纳的答案更好,因为你实际上解释了问题。 - VHS
2
@Oliver那么为什么静态数据成员可以作为默认值呢?它们并不比其他数据成员更可见(因为在调用成员函数时对象将存在)。或者如果静态成员是私有的呢? - User 10482

5
你可以这样重载函数成员:
void Object::MoveTo(double speed, Point position) {
   ....
}

void Object::MoveTo(double speed) {
   Point position = this->initPos;

   MoveTo(speed, position);
}

1
不幸的是,唯一能完成工作的解决方法看起来很丑 :( - Amr Saber
@Argento 有什么不好的呢?在重载中调用其他重载是很常见的。 - Ted Lyngmo
1
@TedLyngmo 我习惯于其他编程语言,比如(JS、Python、Kotlin等等...)与它们相比,考虑到C++已经有了函数默认值的语法,这是一种丑陋的解决方法,而不像其他语言那样默认支持。尽管如此,毕竟这是个人看法问题。 - Amr Saber
@Argento 你提到的编程语言是否支持通过隐式的 *this(或等效语言)来接受默认值? - Ted Lyngmo
@TedLyngmo 在 Kotlin 和 JS 中是可以的,不确定 Python 是否支持 OOP,因为之前没有大量使用过。所以提到的示例将变成类似于 MoveTo(speed, position = initPos) { ... } 的形式。 - Amr Saber

1
除了重载之外,另一种方法是将无效值作为默认值传递。然后在函数内部检查它。
void Object::MoveTo(double speed, Point position = InvalidPosition) {
   position = (position != InvalidPosition) ? position : initPos;
   ...
}

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