在c++中通过引用传递可选参数

问题描述 投票:0回答:13

我在 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'

是否可以在不重载函数的情况下解决?

c++ pass-by-reference optional-parameters
13个回答
52
投票

为什么不能使用函数重载?这肯定是解决您问题的最简单方法吗?

void foo(double &bar, double &foobar) 
{ 
   bar = 100; 
   foobar = 150; 
}

void foo(double &bar) 
{ 
   double foobar = 0.0;
   foo(bar, foobar);
}

46
投票

(可变)引用的默认参数必须是左值。我能想到的最好的,在不超载的情况下,是

static double _dummy_foobar;
void foo(double &bar, double &foobar = _dummy_foobar)

45
投票

不要使用可选参数的引用。不存在引用 NULL 的概念:引用始终是特定对象的别名。

也许看看

boost::optional
std::experimental::optional
boost::optional
甚至专门用于参考类型!

void foo(double &bar, optional<double &> foobar = optional<double &>())

11
投票

另一种方法是使用指针而不是引用。这提供了您想要的语义而不重载。 (就我个人而言,我可能会选择超载。)

void foo(double* bar, double* foobar = 0)
{
   if (bar) *bar = 100;
   if (foobar) *foobar = 150;
}

   // ...

   foo(&mBar, &mFoobar);

   // ...

   foo(&mBar);

   // ...

6
投票

要使用标准库安全地执行此操作,您需要将

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,它还可以非常巧妙地进行优化,并且没有运行时成本。


5
投票

这是另一种不会导致内存泄漏的疯狂方法,您在现实生活中永远不应该使用它,但乍一看似乎符合标准,并且在 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;
}

重申:不要这样做!还有更好的选择!超载可以成为你的朋友!但是,是的,如果你愚蠢且小心,你就可以做到。 :)


2
投票

使用指针类型并将其设置为 NULL 比为引用参数设置默认/可选值要容易得多。


1
投票
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;
}

请参阅了解左值和右值,通过简单的示例获得很好的解释。


0
投票

就面向对象范式而言:如果给定的类具有“默认”,则必须相应地声明此默认,然后可以将其用作“默认参数” 例如:

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:拼写和修复...


0
投票

从 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
来检查参数的正确性。


0
投票

我们希望暂时实现发生。

可能的选择:

  • 将引用绑定到纯右值

    参见我的其他答案

  • 执行会员访问

    double& foobar = *std::make_unique<double>(0.0);
    
  • 执行数组到指针的转换

    double& foobar = std::array<double, 1>{ 0.0 }[0];
    

-2
投票

这就是我解决这个问题的方法:

我原来的函数没有返回错误字符串:

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
,而不需要检查它是否有效,并且不会出现内存泄漏。


-4
投票

你可以这样做疯狂的方式:

void foo(double &bar, double &foobar = (*(new double())))

附注- 我知道这并不令人愉快,但就是这样。还要确保不要留下内存泄漏! :))

© www.soinside.com 2019 - 2024. All rights reserved.