C ++中的默认参数vs重载

时间:2018-02-18 08:47:45

标签: c++ overloading language-lawyer api-design default-arguments

例如,而不是

void shared_ptr::reset() noexcept;
template <typename Y>
void shared_ptr::reset(Y* ptr);

人们可能会想到

template <typename Y = T>
void shared_ptr::reset(Y* ptr = nullptr);

我认为这里的性能差异可以忽略不计,第二个版本更简洁。 C ++标准是第一种方式有什么具体原因吗?

已经要求same question提供Kotlin语言,并且首选默认参数。

更新

std::unique_ptr::reset()遵循默认参数设计(请参阅here)。所以我认为std::shared_ptr::reset()使用重载的原因是因为它们有不同的异常规范。

4 个答案:

答案 0 :(得分:28)

关键的区别在于,这两个操作实际上在语义上并不相同。

第一个意思是让shared_ptr没有托管对象。第二个是让指针管理另一个对象。这是一个重要的区别。在单个函数中实现它意味着我们基本上只有一个函数执行两个不同的操作。

此外,每个操作可能对所讨论的类型具有不同的约束。如果我们将它们转储到一个函数中,那么“两个分支”将必须满足相同的约束,这是不必要的限制。 C ++ 17和constexpr if缓解了它,但在退出之前指定了这些函数。

最终,我认为这个设计符合Scott Meyers的建议。如果默认参数让你在语义上做了不同的事情,它可能应该是另一个重载。

好的,所以要解决你的编辑问题。是的,异常规范是不同的。但正如我之前提到的那样,原因它们可能不同,是函数正在做不同的事情。 semantics of the reset members要求:

void reset() noexcept;
     

效果:相当于shared_­ptr().swap(*this)

template<class Y> void reset(Y* p);
     

效果:相当于shared_­ptr(p).swap(*this)

那里没有大的新闻报道。每个函数都具有使用给定参数(或缺少参数)构建新shared_ptr和交换的效果。那么shared_ptr构造函数的作用是什么?根据{{​​3}},他们这样做:

constexpr shared_ptr() noexcept;
     

效果:构造一个空的shared_ptr对象   后置条件use_­count() == 0 && get() == nullptr

template<class Y> explicit shared_ptr(Y* p);
     

后置条件use_­count() == 1 && get() == p。   抛出bad_­alloc,或者在无法获取内存以外的资源时执行定义的异常

注意指针使用次数的不同后置条件。这意味着第二次超载需要考虑任何内部簿记。并且很可能为它分配存储空间。两个重载的构造函数做了不同的事情,就像我之前所说的那样,这是将它们分成不同函数的强烈暗示。可以获得更强的异常保证的事实进一步证明了该设计选择的合理性。

最后,为什么unique_ptr对这两个动作只有一个重载?因为默认值不会更改语义。它只需要跟踪新的指针值。值为null(来自默认参数或其他)的事实不会大幅改变函数的行为。因此,单个过载是合理的。

答案 1 :(得分:7)

如果你是OFTEN重置为精确nullptr而不是新值,那么单独的函数void shared_ptr::reset() noexcept;将具有空间优势,因为你可以对所有类型{{1}使用一个函数},而不是具有针对每种类型Y采用Y类型的特定函数。另一个空间优势是没有参数的实现不需要传递给函数的参数。

当然,如果多次调用函数,这两者都不重要。

异常行为也存在差异,这可能非常重要,我相信这就是为什么标准有多个此函数声明的动机。

答案 2 :(得分:7)

虽然其他答案的设计选择都是有效的,但它们确实假设了一件在这里不完全适用的事情:语义等价!

void shared_ptr::reset() noexcept;
                      // ^^^^^^^^
template <typename Y>
void shared_ptr::reset(Y* ptr);

第一个重载是noexcept,而第二个重载不是。{0}。根据参数的运行时值无法确定noexcept - ness,因此需要不同的重载。

有关不同noexcept规范的原因的一些背景信息: reset()不抛出,因为假设先前包含的对象的析构函数不抛出。但是第二个重载可能还需要为共享指针状态分配一个新的控制块,如果分配失败,它将抛出std::bad_alloc。 (reset nullptr可以在不分配控制块的情况下完成cor(M)[c("a", "b", "c"), c("A", "B", "C")] # A B C # a -0.7071068 0.3333333 0.0000000 # b 0.5000000 0.7071068 -1.0000000 # c -0.3162278 -0.4472136 0.6324555 。)

答案 3 :(得分:3)

重载和默认指针之间存在根本区别:

  • 重载是自包含的:库中的代码完全独立于调用上下文。
  • 默认参数不是自包含的,但取决于调用上下文中使用的声明。它可以在给定范围内使用简单声明重新定义(例如,不同的默认值,或者不再有默认值。

从语义上讲,默认值是调用代码中嵌入的快捷方式,而重载是嵌入在被调用代码中的含义。