首页 文章

公共交换成员函数

提问于
浏览
127

copy-and-swap-idiom的漂亮答案中,有一段代码我需要一些帮助:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

他补充说明

还有其他声称我们应该专门为我们的类型使用std :: swap,提供一个类内交换以及一个自由函数交换等等 . 但这都是不必要的:任何正确使用swap都将通过不合格调用,我们的功能将通过ADL找到 . 一个功能就可以了 .

我必须承认, friend 我有点"unfriendly"条款 . 所以,我的主要问题是:

  • looks like a free function ,但它在 class 体内吗?

  • why isn't this swap static ?它显然不使用任何成员变量 .

  • "Any proper use of swap will find out swap via ADL" ? ADL会搜索命名空间,对吧?但它也看到了课程内部吗?或者 friend 在哪里?

副题:

  • 使用C 11,我应该用 noexcept 标记 swap 吗?

  • 使用C 11及其 range-for ,我应该在课程中以相同的方式放置 friend iter begin()friend iter end() 吗?我觉得这里不需要 friend 对吧?

2 回答

  • 5

    有几种方法可以写 swap ,有些方法比其他方法更好 . 但是,随着时间的推移,发现单一定义效果最好 . 让我们考虑如何考虑编写 swap 函数 .


    我们首先看到像 std::vector<> 这样的容器有一个单参数成员函数 swap ,例如:

    struct vector
    {
        void swap(vector&) { /* swap members */ }
    };
    

    当然,我们的 class 也应该,对吧?嗯,不是真的 . 标准库有all sorts of unnecessary things,成员 swap 就是其中之一 . 为什么?我们继续 .


    我们应该做的是确定什么是规范的,以及我们的课程需要做些什么才能使用它 . 规范的交换方法是 std::swap . 这就是为什么成员函数通常不应该如何交换东西,而且与 std::swap 的行为无关 .

    那么,为了使 std::swap 工作,我们应该提供(并且 std::vector<> 应该提供) std::swap 的专业化,对吧?

    namespace std
    {
        template <> // important! specialization in std is OK, overloading is UB
        void swap(myclass&, myclass&)
        {
            // swap
        }
    }
    

    那么这肯定会在这种情况下起作用,但它有一个明显的问题:功能专业化不能是局部的 . 也就是说,我们不能用这个来专门化模板类,只有特定的实例化:

    namespace std
    {
        template <typename T>
        void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
        {
            // swap
        }
    }
    

    这种方法在某些时候有效,但不是所有时间都有效 . 肯定有更好的办法 .


    有!我们可以使用 friend 函数,并通过ADL找到它:

    namespace xyz
    {
        struct myclass
        {
            friend void swap(myclass&, myclass&);
        };
    }
    

    当我们要交换某些内容时,我们会关联† std::swap 然后进行无条件的通话:

    using std::swap; // allow use of std::swap...
    swap(x, y); // ...but select overloads, first
    
    // that is, if swap(x, y) finds a better match, via ADL, it
    // will use that instead; otherwise it falls back to std::swap
    

    什么是 friend 功能?这个地区有混乱 .

    在C标准化之前, friend 函数执行了一个名为"friend name injection"的函数,其中代码的行为就好像函数是在周围的命名空间中编写的一样 . 例如,这些是等效的预标准:

    struct foo
    {
        friend void bar()
        {
            // baz
        }
    };
    
    // turned into, pre-standard:    
    
    struct foo
    {
        friend void bar();
    };
    
    void bar()
    {
        // baz
    }
    

    然而,当发明ADL时,这被删除了 . 然后只能通过ADL找到 friend 函数;如果你想把它作为一个自由函数,它需要被声明为(例如see this) . 但是,瞧!有一个问题 .

    如果你只是使用 std::swap(x, y) ,你的重载将永远不会被发现,因为你明确地说过“查看 std ,而不是其他地方”!这就是为什么有些人建议编写两个函数的原因:一个是通过ADL找到的函数,另一个是处理显式的 std:: 资格 .

    但是就像我们看到的那样,这可以't work in all cases, and we end up with an ugly mess. Instead, idiomatic swapping went the other route: instead of making it the classes'工作提供 std::swap ,它's the swappers'工作以确保他们不使用合格的 swap ,就像上面一样 . 只要人们了解它,这往往效果很好 . 但其中存在的问题是:需要使用不合格的电话是不直观的!

    为了使这更容易,像Boost这样的库提供了函数 boost::swap ,它只对 swap 进行了非限定调用, std::swap 作为关联的命名空间 . 这有助于使事情再次简洁,但它仍然是一个无赖 .

    请注意,C11对 std::swap 的行为没有任何变化,我和其他人错误地认为是这种情况 . 如果你有点,read here .


    简而言之:成员函数只是噪声,专业化是丑陋和不完整的,但 friend 函数是完整的并且有效 . 当你交换时,使用 boost::swap 或不合格的 swapstd::swap 相关联 .


    †非正式地,如果在函数调用期间考虑名称,则关联名称 . 有关详细信息,请阅读§3.4.2 . 在这种情况下,通常不考虑 std::swap ;但是我们可以将它关联起来(将它添加到由不合格的 swap 考虑的重载集),允许它被找到 .

  • 137

    该代码相当于(几乎在所有方面):

    class dumb_array
    {
    public:
        // ...
        friend void swap(dumb_array& first, dumb_array& second);
        // ...
    };
    
    inline void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    

    在类中定义的友元函数是:

    • 放在封闭的命名空间中

    • 自动 inline

    • 能够在没有进一步限定的情况下引用该类的静态成员

    确切的规则在 [class.friend] 部分(我引用C 0x草案的第6和第7段):

    一个功能可以在类的友元声明中定义当且仅当该类是非本地类(9.8)时,函数名称是非限定的,并且该函数具有命名空间作用域 . 这样的功能是隐含的内联 . 类中定义的友元函数位于定义它的类的(词法)范围内 . 在课外定义的朋友函数不是 .

相关问题