我在 C++ 中遇到可选函数参数的问题
我想做的是编写带有通过引用传递的可选参数的函数,这样我就可以以两种方式使用它(1)和(2),但在(2)上我并不关心什么是
mFoobar
的值。
我尝试过这样的代码:
void foo(double &bar, double &foobar = NULL)
{
bar = 100;
foobar = 150;
}
int main()
{
double mBar(0),mFoobar(0);
foo(mBar,mFoobar); // (1)
cout << mBar << mFoobar;
mBar = 0;
mFoobar = 0;
foo(mBar); // (2)
cout << mBar << mFoobar;
return 0;
}
但这行代码无法编译
void foo(double &bar, double &foobar = NULL)
留言:
error: default argument for 'double& foobar' has type 'int'
是否可以在不重载函数的情况下解决?
为什么不能使用函数重载?这肯定是解决您问题的最简单方法吗?
void foo(double &bar, double &foobar)
{
bar = 100;
foobar = 150;
}
void foo(double &bar)
{
double foobar = 0.0;
foo(bar, foobar);
}
(可变)引用的默认参数必须是左值。我能想到的最好的,在不超载的情况下,是
static double _dummy_foobar;
void foo(double &bar, double &foobar = _dummy_foobar)
不要使用可选参数的引用。不存在引用 NULL 的概念:引用始终是特定对象的别名。
boost::optional
或std::experimental::optional
。 boost::optional
甚至专门用于参考类型!
void foo(double &bar, optional<double &> foobar = optional<double &>())
另一种方法是使用指针而不是引用。这提供了您想要的语义而不重载。 (就我个人而言,我可能会选择超载。)
void foo(double* bar, double* foobar = 0)
{
if (bar) *bar = 100;
if (foobar) *foobar = 150;
}
// ...
foo(&mBar, &mFoobar);
// ...
foo(&mBar);
// ...
要使用标准库安全地执行此操作,您需要将
std::optional
与 std::reference_wrapper
结合使用。 std::optional<T&>
形式的可选引用在 C++17 中是非法的。
#include <optional>
#include <functional>
void foo(double& bar, std::optional<std::reference_wrapper<double>> foobar = {})
{
if(foobar) // If the user has passed a reference
{
foobar->get() = 1.0; // Assign values to the reference
}
}
这对于被调用者来说是完全透明的。您可以像平常一样调用这样的函数:
double a {}, b {};
foo(b, a);
std::cout << a; // Prints 1.0;
这种方法的优点是它有一个空值来指示用户是否确实传递了引用。使用 -O2/-O3,它还可以非常巧妙地进行优化,并且没有运行时成本。
这是另一种不会导致内存泄漏的疯狂方法,您在现实生活中永远不应该使用它,但乍一看似乎符合标准,并且在 Cygwin 下使用 Visual C++ 2008 和 g++ 3.4.4 进行编译:
void foo(double &bar, double &foobar = (*((double*)0)))
{
bar = 100;
double* pfoobar = &foobar;
if (pfoobar != 0)
foobar = 150;
}
重申:不要这样做!还有更好的选择!超载可以成为你的朋友!但是,是的,如果你愚蠢且小心,你就可以做到。 :)
使用指针类型并将其设置为 NULL 比为引用参数设置默认/可选值要容易得多。
void foo(double &bar,
double &foobar = const_cast<double &>(static_cast<const double &>(0.0)))
{
bar = 100;
foobar = 150;
}
这里发生的是,我们首先创建一个值为
0.0
的临时变量。
使用static_cast
,我们获得参考,并且临时的寿命得以延长。
在这个阶段我们不能直接转换为可变的。
由于原始临时没有标记为const
,我们可以使用const_cast
来获得可变引用,一般不建议使用,但适合这种情况。
通过将结果分配给名为 foobar
的命名变量,可以延长寿命,因此我们可以拥有一个可选的输出参数而无需函数重载。
我同意之前的解决方案很丑陋而且太冗长。有一种更好的方法,其背后具有相同的逻辑。使用 C++11,我们可以受益于右值引用 (
&&
)。
template <class T>
T& make_ref(T&& x) { return x; }
void foo(double &bar, double &foobar = make_ref(0.0))
{
bar = 100;
foobar = 150;
}
请参阅了解左值和右值,通过简单的示例获得很好的解释。
就面向对象范式而言:如果给定的类具有“默认”,则必须相应地声明此默认,然后可以将其用作“默认参数” 例如:
class Pagination {
private:
int currentPage;
public:
//...
Pagination() {
currentPage = 1;
//...
}
// your Default Pagination (Must be initialized before thread concurrency)
static Pagination& Default() {
static Pagination p;
return p;
}
};
关于你的方法...
//...
std::vector<User>
findByFilter(User& audit, Pagination& p = Pagination::Default() ) {
// ...
编辑:此解决方案非常合适,因为在这种情况下它是“全局默认”分页和单个“参考”值。您还可以更改默认值,例如导航/显示首选项等。
编辑2:拼写和修复...
从 C++17 开始,您可以使用参数包来实现此功能,无需手动重载,也无需运行时开销,如下所示:
template <typename... OptArgType> void foo(double &bar, OptArgType&... foobar)
{
static_assert(sizeof...(OptArgType) <= 1 && (std::is_same_v<OptArgType, double> && ...));
bar = 100;
((foobar = 150), ...); // use fold expression for assignment
}
运行时不需要“是否提供了此参数”检查,折叠表达式将扩展到代码,仅用于实际将参数传递给函数的调用。
除了
static_assert
之外,可以使用std::enable_if
来检查参数的正确性。
这就是我解决这个问题的方法:
我原来的函数没有返回错误字符串:
bool MyClass::validateXML(const QString& fileName, const QUri& schemaUri);
我想将验证结果添加到错误字符串中,所以我实现了:
bool MyClass::validateXML(const QString& fileName,
const QUri& schemaUri,
QString& errorString = *(std::make_unique<QString>().get()));
这样就可以在
errorString
中引用validateXML
,而不需要检查它是否有效,并且不会出现内存泄漏。
你可以这样做疯狂的方式:
void foo(double &bar, double &foobar = (*(new double())))
附注- 我知道这并不令人愉快,但就是这样。还要确保不要留下内存泄漏! :))