29

背景

对于这个问题,请考虑以下代码:

#include <utility>

namespace ns
{
    struct foo
    {
        foo() : i(0) {}
        int i;
        
    private:
        foo(const foo&); // not defined,
        foo& operator=(const foo&); // non-copyable
    };
    
    void swap(foo& lhs, foo& rhs)
    {
        std::swap(lhs.i, rhs.i);
    }
}

template <typename T>
void do_swap(T& lhs, T& rhs); // implementation to be determined

int main()
{
    ns::foo a, b;
    do_swap(a, b);
}

在 C++03 中,这个实现do_swap将被认为是“损坏的”:

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::swap(lhs, rhs);
}

通过显式指定std::,它禁止ns::swap通过依赖于参数的查找找到。(然后编译失败,因为std::swap试图复制 a foo,这是不允许的。)相反,我们这样做:

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    using std::swap; // allow std::swap as a backup if ADL fails to find a swap
    swap(lhs, rhs); // unqualified call to swap, allow ADL to operate
}

Nowns::swap被发现std::swap,并且由于不那么专业,所以不使用。它更丑陋,但它有效并且事后看来是可以理解的。boost::swap为我们很好地包装了这个(并提供了数组重载):

#include <boost/swap.hpp>

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    boost::swap(lhs, rhs); // internally does what do_swap did above
}

问题

因此,我的问题是:是否具有C++11 中std::swap的行为?boost::swap如果不是,为什么?

对我来说,这似乎很明显。任何被更改破坏的代码一开始都可能非常脆弱(算法和容器,如std::sortand std::vector,未指定;允许实现调用 ADL 交换或不确定地调用),因此更改会更好。另外,std::swap现在是为数组定义的,所以改变肯定不是不可能的。

然而,虽然 §17.6.3.2 规定swap标准库中的所有调用都必须在没有std::限定的情况下完成(解决上述算法和容器的问题),但它并没有涉及到std::swap自身。它甚至给出了交换值的示例,包括using std::swap;. 同样,第 20.2.2 节(在哪里std::swap指定)没有对 ADL 说一句话。

最后,GCC 并没有在他们的std::swap实现中启用 ADL(MSVC 也没有,但这没什么好说的)。所以我一定是错的,std::swap采取的行为boost::swap,但我不明白为什么没有做出改变。:(而且我并不孤单

4

4 回答 4

30

如果您提出了概念验证实施方案,我将不得不投票反对。我担心它会破坏以下代码,我很确定在过去的十几年中我至少在野外看到过一次或两次。

namespace oops
{

    struct foo
    {
        foo() : i(0) {}
        int i;

        void swap(foo& x) {std::swap(*this, x);}
    };

    void swap(foo& lhs, foo& rhs)
    {
        lhs.swap(rhs);
    }

}

无论您认为上面的代码是好还是坏,它都按照作者在 C++98/03 中的意图工作,因此默默地破坏它的门槛相当高。告诉用户在 C++11 中他们将不再需要编写using std::swap;并不足以抵消将上述代码静默转换为无限递归的缺点。

另一种摆脱写作using std::swap;的方法是使用std::iter_swap

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::iter_swap(&lhs, &rhs); // internally does what do_swap did above
}
于 2012-02-16T21:05:46.630 回答
9

在 C++20 中,这终于标准化了:

std::swap(a, b);

这使用 ADL 调用正确的重载并施加正确的要求以在 SFINAE 中使用。魔法在[namespace.std]/7中指定:

除了在命名空间std或命名空间内的命名空间 之外std,程序可以为任何指定为自定义点的库函数模板提供重载,前提是 (a) 重载的声明取决于至少一个用户定义的类型和 (b)重载满足定制点的标准库要求。174 [ <em>注意:这允许对自定义点进行(合格或不合格)调用,以调用给定参数的最合适的重载。— <em>结束注释]

174)任何库定制点都必须准备好充分处理满足本文档最低要求的任何用户定义的重载。因此,实现可以根据 as-if 规则 ([intro.execution]) 选择以实例化函数对象 ([function.objects]) 的形式提供任何定制点,即使定制点的规范采用以下形式的功能模板。每个这样的函数对象的模板参数和对象的函数参数和返回类型operator()必须与相应的定制点的规范相匹配。

(强调我的)

并在[utility.swap]swap中指定为自定义点:

template<class T>
  constexpr void swap(T& a, T& b) noexcept(see below);

备注: 此函数是一个指定的自定义点is_­move_­constructible_­v<T>([namespace.std]),除非istrueis_­move_­assignable_­v<T>is ,否则不应参与重载解析true。里面的表达式 noexcept等价于:

is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>

要求:类型T应为Cpp17MoveConstructible(表 26)和Cpp17MoveAssignable(表 28)。

效果:交换存储在两个位置的值。

(强调我的)

于 2019-08-05T04:58:51.620 回答
3

这是一个概念验证实现:

#include <utility>

// exposition implementation
namespace std_
{
    namespace detail
    {
        // actual fallback implementation
        template <typename T>
        void swap(T& lhs, T& rhs)
        {
            T temp = std::move(lhs);
            lhs = std::move(rhs);
            rhs = std::move(temp);
        }
    }

    template <typename T>
    void swap(T& lhs, T& rhs)
    {
        using detail::swap; // shadows std_::swap, stops recursion
        swap(lhs, rhs); // unqualified call, allows ADL
    }
}

namespace ns
{
    struct foo
    {
        foo() : i(0) {}
        int i;

    private:
        foo(const foo&); // not defined,
        foo& operator=(const foo&); // non-copyable
    };

    void swap(foo& lhs, foo& rhs)
    {
        std::swap(lhs.i, rhs.i);
    }
}


int main()
{
    int i = 0, j = 0;
    std_::swap(i, j);

    ns::foo a, b;
    std_::swap(a, b);
}
于 2012-02-07T03:04:17.167 回答
1

嗯,boost::swap()派人去std::swap()。要做std::swap()类似的boost::swap()事情需要委托其他地方。这是别的什么地方?该标准不要求swap()提供实际实现的另一个版本。这可以做到,但标准没有强制要求。

为什么它不这样做?我没有看到任何提议此实施的提案。如果有人希望这样做,我相信它会被提议。

于 2012-02-07T02:50:46.117 回答