Pimpl习语和交换

问题描述 投票:3回答:2

我有几个基于PIMPL习惯用法的类(其中unique_ptr指的是实际的实现结构)。

我尚未添加friend swap函数(如here所述),因为据我所知,标准std::swap使用移动语义,可以很好地交换unique_ptr。到目前为止,一切都很好。

但是,我阅读了(斯科特·迈耶斯(Scott Meyers)在第25项中说的有些过时的Effective C++

但是,默认的交换实现可能不会让您感到兴奋。它涉及到复制三个对象:a到temp,b到a和temp到b。 [...]对于某些类型,默认交换使您进入快速通道,进入慢速通道。这些类型中最重要的是那些主要由指向包含实际数据的其他类型的指针组成的类型。这种设计的一个常见体现是“ pimpl”惯用语。

[之后,他还建议也专门研究std::swap

我的问题是,这在C ++ 11中是否仍然有效。看来C ++ 11 swap对于pimpl'd类来说工作得很好。我知道添加friend swap允许STL使用ADL等等,但是我更喜欢使类尽可能精简。

c++ c++11 swap unique-ptr pimpl-idiom
2个回答
2
投票

我的问题是,这在C ++ 11中是否仍然适用。

仅在较小程度上。

自从C ++ 11中引入移动语义以来,通用交换不再复制,而是移动。

移动通常与最佳交换实现非常接近,以至于通常无需费心编写一个自定义实现。尽管它可能接近最佳状态,但是在许多情况下,自定义实现可能会稍微更理想一些。可以通过测量性能来确定编写自定义代码是否有益。


0
投票

这里的问题可能是,std::unique_ptr实现的PIMPL,您基本上需要在头文件之外定义move构造函数/赋值运算符和析构函数(请参阅Meyers的Effective Modern C ++项目22)。然后,std::swap不会“看到”这些定义,并且编译器无法优化掉不必要的操作,例如空指针的设置等。由于没有其他选择,它将仅生成4条call指令。

考虑该课程的simple demo

class X
{
    public: 
        X(X&&);
        X& operator=(X&&);
        ~X();

        void swap(X& other) { std::swap(pimpl_, other.pimpl_); }

    private:
        class Impl;
        std::unique_ptr<Impl> pimpl_;
};

由GCC使用-O3生成的程序集,该程序集使用X交换两个std::swap对象,如下所示:

f1(X&, X&):
        push    r12
        mov     r12, rdi
        push    rbp
        mov     rbp, rsi
        mov     rsi, rdi
        sub     rsp, 24
        lea     rdi, [rsp+8]
        call    X::X(X&&)
        mov     rsi, rbp
        mov     rdi, r12
        call    X::operator=(X&&)
        lea     rsi, [rsp+8]
        mov     rdi, rbp
        call    X::operator=(X&&)
        lea     rdi, [rsp+8]
        call    X::~X() [complete object destructor]
        add     rsp, 24
        pop     rbp
        pop     r12
        ret
        mov     rbp, rax
        jmp     .L2
f1(X&, X&) [clone .cold]:
.L2:
        lea     rdi, [rsp+8]
        call    X::~X() [complete object destructor]
        mov     rdi, rbp
        call    _Unwind_Resume

虽然通过X::swap为同一操作生成的程序集为:

f2(X&, X&):
        mov     rax, QWORD PTR [rdi]
        mov     rdx, QWORD PTR [rsi]
        mov     QWORD PTR [rdi], rdx
        mov     QWORD PTR [rsi], rax
        ret

后者显然是最佳的,因为它只涉及交换两个常规指针所必需的指令(在我们的示例中隐藏在std::unique_ptr后面。

© www.soinside.com 2019 - 2024. All rights reserved.