C++ Pimpl习语与交换
我有几个基于PIMPL习惯用法的类(其中,C++ Pimpl习语与交换,c++,c++11,swap,unique-ptr,pimpl-idiom,C++,C++11,Swap,Unique Ptr,Pimpl Idiom,我有几个基于PIMPL习惯用法的类(其中,unique\u ptr指的是实际的实现结构) 我没有添加friendswap函数(如所述),因为据我所知,标准std::swap使用移动语义,可以很好地交换唯一的ptrs。到目前为止,一切顺利 然而,我从Scott Meyers那里读到(有些过时的高效C++),在第25项中说: 但是,默认的交换实现可能不会让您激动,它涉及到复制三个对象:a到temp、b到a和temp到b.[…]对于某些类型,默认交换会使您走上快车道,进入慢车道。在这些类型中,最重要的
unique\u ptr
指的是实际的实现结构)
我没有添加friend
swap
函数(如所述),因为据我所知,标准std::swap
使用移动语义,可以很好地交换唯一的ptr
s。到目前为止,一切顺利
然而,我从Scott Meyers那里读到(有些过时的高效C++
),在第25项中说:
但是,默认的交换实现可能不会让您激动,它涉及到复制三个对象:a到temp、b到a和temp到b.[…]对于某些类型,默认交换会使您走上快车道,进入慢车道。在这些类型中,最重要的是那些主要由指向另一个包含真实数据的类型的指针组成的类型。这种设计的常见表现形式是“pimpl”习惯用法
之后,他还建议专门研究std::swap
我的问题是,这在C++11中是否仍然有效。似乎C++11swap
对于pimpl类来说效果很好。我知道添加一个friend
swap
允许STL使用依赖于参数的查找等,但我更喜欢尽可能精简我的类
我的问题是,这在C++11中是否仍然有效
只是程度要小得多
自从在C++11中引入移动语义以来,通用交换不再复制,而是移动
移动通常更接近于最佳交换实现,因此通常不需要编写自定义实现自定义实现可能会更好。编写自定义实现是否足够快,是否有益,可以通过测量性能来确定。这里的问题可能是,对于由
std::unique\u ptr
实现的PIMPL,您基本上需要在f头文件(参见Meyers的《有效的现代C++》第22项)。然后,std::swap
没有“参见”这些定义和编译器无法优化掉不必要的操作,例如不再使用的空指针设置、使用空指针参数调用运算符delete
等。它只会生成4个调用
指令,因为它没有其他选项
考虑针对此类的一个示例:
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_;
};
使用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
后面)
此外,即使对于空的
X::Impl
类,所涉及的特殊成员函数的生成程序集也有许多指令:
X::X(X&&):
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rdi], rax
mov QWORD PTR [rsi], 0
ret
X::operator=(X&&):
push r12
mov r12, rdi
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rsi], 0
mov rdi, QWORD PTR [rdi]
mov QWORD PTR [r12], rax
test rdi, rdi
je .L7
mov esi, 1
call operator delete(void*, unsigned long)
.L7:
mov rax, r12
pop r12
ret
X::~X() [base object destructor]:
mov rdi, QWORD PTR [rdi]
test rdi, rdi
je .L12
mov esi, 1
jmp operator delete(void*, unsigned long)
.L12:
ret
我认为,在某些情况下,定制实现甚至可以更为优化(例如,请参见我的答案)@DanielLangr相对差异可能很大,但我仍然希望通用交换非常快。它不是非常非常快。这是否会产生差异很大程度上取决于你将花多少时间交换。
X::X(X&&):
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rdi], rax
mov QWORD PTR [rsi], 0
ret
X::operator=(X&&):
push r12
mov r12, rdi
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rsi], 0
mov rdi, QWORD PTR [rdi]
mov QWORD PTR [r12], rax
test rdi, rdi
je .L7
mov esi, 1
call operator delete(void*, unsigned long)
.L7:
mov rax, r12
pop r12
ret
X::~X() [base object destructor]:
mov rdi, QWORD PTR [rdi]
test rdi, rdi
je .L12
mov esi, 1
jmp operator delete(void*, unsigned long)
.L12:
ret