C++中如何在构造函数初始化列表中条件选择成员构造函数
问题场景
我定义了一个std::vector的包装类MyVector,目前有两个移动构造函数:
template <class Allocator> class MyVector { // ... MyVector(MyVector&&) = default; MyVector(MyVector&& other, const Allocator<int>& alloc) : vec(std::move(other.vec), alloc) {} private: std::vector<int, Allocator<int>> vec; // ... };
我想做一个优化:当传入的分配器与被移动对象的分配器相同时,避免调用std::vector的高开销构造函数,期望实现类似如下逻辑:
class MyVector { MyVector(MyVector&& other, const Allocator<int>& alloc) : if (other.vec.get_allocator() == alloc) vec(std::move(other.vec)) else vec(std::move(other.vec), alloc) {} };
请问这种逻辑在C++中是否可行?
注:问题《Right way to conditionally initialize a C++ member variable?》与此不相似,因为我无法将条件逻辑嵌入到构造函数调用内部,必须在外部选择要调用的构造函数。
背景
无法修改的第三方库代码错误地调用了带分配器参数的移动构造函数,这严重影响性能,我正尝试修复该问题。
更多背景:问题出在std::scoped_allocator_adaptor上,它将std::pair视为容器从而引发问题。当使用set<pair<int,MyVector>>并为所有内存分配使用同一个作用域分配器时,allocator_traits::construct()会生成错误的构造函数调用。被移动的MyVector实际上使用相同的分配器,但由于pair的存在,vec.get_allocator() == set.get_allocator()这一事实被忽略,导致pair构造时调用了带不必要alloc参数的MyVector移动构造函数。
解决方案
这种条件初始化逻辑是可行的,但不能直接在构造函数初始化列表中写if-else语句(初始化列表仅支持表达式,不支持语句块)。可以通过两种方式实现:
方式1:使用条件运算符简化实现
利用C++的条件运算符(?:)在初始化列表中直接选择不同的初始化方式,确保两个分支返回的类型一致:
template <class Allocator> class MyVector { // ... MyVector(MyVector&&) = default; MyVector(MyVector&& other, const Allocator<int>& alloc) : vec(other.vec.get_allocator() == alloc ? std::move(other.vec) : std::vector<int, Allocator<int>>(std::move(other.vec), alloc)) {} private: std::vector<int, Allocator<int>> vec; // ... };
当分配器相同时,直接移动other.vec(触发std::vector的普通移动构造,无额外内存分配);当分配器不同时,调用带分配器参数的移动构造函数。
方式2:使用辅助函数提升可读性
如果条件逻辑复杂(比如后续需要扩展判断逻辑),可以封装一个私有辅助函数来处理初始化逻辑,再在构造函数中调用:
template <class Allocator> class MyVector { // ... MyVector(MyVector&&) = default; MyVector(MyVector&& other, const Allocator<int>& alloc) : vec(create_vector(std::move(other), alloc)) {} private: std::vector<int, Allocator<int>> vec; static std::vector<int, Allocator<int>> create_vector(MyVector&& other, const Allocator<int>& alloc) { if (other.vec.get_allocator() == alloc) { return std::move(other.vec); } else { return std::vector<int, Allocator<int>>(std::move(other.vec), alloc); } } // ... };
原理说明
std::vector的带分配器移动构造函数(vector(vector&&, const Allocator&))在分配器不同时,会将原vector的元素移动到新分配的内存中,这会带来额外的内存分配和元素移动开销;而当分配器相同时,普通移动构造函数(vector(vector&&))仅转移内存控制权,开销极低。通过上述条件判断,可以在分配器相同时复用普通移动构造的优化,避免不必要的性能损耗,正好解决你遇到的std::scoped_allocator_adaptor导致的错误调用问题。
内容的提问来源于stack exchange,提问作者DanielHsH

