提供可选参数的现代 C++ 方法

Modern C++ approach for providing optional arguments

让我们采用以下函数声明:

void print(SomeType const* i);

这里,参数 iconst* 性质暗示了参数的意图,即参数是 可选的 ,因为它可能是 nullptr.如果这不是故意的,那么参数将只是一个 const&。传达可选-语义当然不是设计指针的初衷,但使用它们这样做恰好在很长一段时间内工作得很好。

现在,由于在现代 C++ 中通常不鼓励使用原始指针(并且应该避免使用 std::unique_ptrstd::shared_ptr 以精确指示特定的 所有权 -语义),我想知道如何正确指示函数参数'可选-语义而不传递值,我。 e. 正在复制,作为

void print(std::optional<SomeType> i);

会做。

经过一段时间的思考,我想到了使用的想法:

void print(std::optional<SomeType const&> i);

这实际上是最精确的。但事实证明 std::optional cannot have reference types

另外,使用

void print(std::optional<SomeType> const& i);

绝不是最优的,因为那时我们会 要求 我们的 SomeType 存在于 调用者的 std::optional-side,再次可能(或者更确切地说 likely)需要副本 there.

问题:那么什么是允许可选参数而不复制的现代好方法呢?在现代 C++ 中,在这里使用原始指针仍然是一种合理的方法吗?


¹:具有讽刺意味的是,std::optional 不能有引用类型的原因(关于重新绑定或转发分配的争议)不适用于 std::optional 的情况const 引用,因为它们不能被赋值给.

接受原始指针非常好,并且仍然在大量“现代”代码库中完成(我会注意到这是一个 fast-moving 目标)。只需对该函数发表评论,说明它允许为 null 以及函数是否在调用后保存指针的副本(即 pointed-to 值的生命周期要求是多少)。

函数重载是否提供了一个干净的解决方案?例如。要同时声明函数的 const ref 和空参数列表版本?
这可能取决于函数体在无 argument/null 情况下的作用 - 以及如何管理这两个实现以最大程度地减少代码重叠。

对于这种类型的可选参数传递,原始指针通常没问题,实际上这是少数可以整体使用原始指针的情况之一。这也是规范推荐的方式。

话虽这么说,boost::optional 确实允许您使用引用可选和常量引用可选。决定不在 std 库中添加此功能(出于我在此处省略的原因)。

Here, the const* nature of the argument i suggest the intent, that the parameter is optional since it may be nullptr.

[...]

So what would be a nice modern approach for allowing optional arguments without copying?

允许可选参数(不是 std::optional 意义上的,而是语义意义上的)基于可选参数是否存在的不同实现变体听起来像是重载的理想候选者:

struct SomeType { int value; };

namespace detail {
    void my_print_impl(const SomeType& i) {
        std::cout << i.value;
    }    
}  // namespace detail

void my_print() {
    const SomeType default_i{42};
    detail::my_print_impl(default_i);
}

void my_print(const SomeType& i) { 
    detail::my_print_impl(i);
}

namespace detail {
    void my_print_impl() {
        std::cout << "always print me\n";
    }    
}  // namespace detail

void my_print() {
    detail::my_print_impl();
}

void my_print(const SomeType& i) {
    detail::my_print_impl();
    std::cout << "have some type: " << i.value;
}

或一些类似的变体,具体取决于您的实现应该根据可选参数的 existence/non-existence 执行的操作。


可选引用,除此之外,基本上是原始指针,后者也可以使用(如果重载不适用)。

这实际上就是 std::reference_wrapper 的用途。另请参阅 Does it make sense to combine optional with reference_wrapper? 以了解有关何时使用它以及何时不使用它的更多推理。