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

Lin*_*gxi 16 c++ api-design overloading language-lawyer default-arguments

例如,而不是

void shared_ptr::reset() noexcept;
template <typename Y>
void shared_ptr::reset(Y* ptr);
Run Code Online (Sandbox Code Playgroud)

人们可能会想到

template <typename Y = T>
void shared_ptr::reset(Y* ptr = nullptr);
Run Code Online (Sandbox Code Playgroud)

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

同样的问题已经被问了科特林语言,默认参数为首选那里.

更新:

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

Sto*_*ica 28

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

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

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

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


好的,所以要解决你的编辑问题.是的,异常规范是不同的.但就像我之前提到的那样,它们可能不同的原因是功能正在做不同的事情.成员语义reset要求:

void reset() noexcept;
Run Code Online (Sandbox Code Playgroud)

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

template<class Y> void reset(Y* p);
Run Code Online (Sandbox Code Playgroud)

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

那里不是一个大新闻.每个函数都shared_ptr具有使用给定参数(或缺少参数)和交换构造new的效果.那么shared_ptr构造函数的作用是什么?根据前一节,他们这样做:

constexpr shared_ptr() noexcept;
Run Code Online (Sandbox Code Playgroud)

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

template<class Y> explicit shared_ptr(Y* p);
Run Code Online (Sandbox Code Playgroud)

后置条件:use_­count() == 1 && get() == p. 抛出:bad_­alloc或者,当无法获得除内存之外的资源时,实现定义的异常

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

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

  • >>*如果默认参数让你在语义上做了不同的事情,它可能应该是另一个重载.*很好. (13认同)
  • 我相信我过去自然会遵循这个建议,而没有真正考虑过它.实际上阅读它明确说明很有意义.我现在会更加清楚这一点 - 谢谢! (3认同)

Mat*_*son 7

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

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

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


and*_*ras 6

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

void shared_ptr::reset() noexcept;
                      // ^^^^^^^^
template <typename Y>
void shared_ptr::reset(Y* ptr);
Run Code Online (Sandbox Code Playgroud)

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

关于不同noexcept规范的原因的一些背景信息: reset()不抛出,因为假设先前包含的对象的析构函数不抛出.但是第二个重载可能还需要为共享指针状态分配一个新的控制块,std::bad_alloc如果分配失败,它将抛出.(和reset婷到nullptr可以不分配控制模块来完成.)