如何单元测试自定义类容器的memcpy拷贝优化是否生效?
验证自定义容器拷贝构造的memcpy优化方案
针对自定义容器的memcpy优化验证,核心是确认可平凡复制类型在拷贝时未调用元素的拷贝构造函数,而是直接通过内存块复制完成。以下是几种可行的测试方法:
方法1:对比平凡/非平凡类型的拷贝构造调用次数
通过构造两种测试类型,分别验证拷贝行为:
- 非平凡复制类型:显式定义带计数的拷贝构造函数,验证容器拷贝时会逐个调用元素的拷贝构造:
#include <atomic> #include <cassert> struct NonTrivialFoo { int val; static std::atomic<int> copy_count; NonTrivialFoo(int v = 0) : val(v) {} NonTrivialFoo(const NonTrivialFoo& other) : val(other.val) { copy_count++; } }; std::atomic<int> NonTrivialFoo::copy_count = 0; // 测试非平凡类型的拷贝次数 void test_non_trivial_copy() { my_vector<NonTrivialFoo> v1{NonTrivialFoo{1}, NonTrivialFoo{2}, NonTrivialFoo{3}}; NonTrivialFoo::copy_count = 0; my_vector<NonTrivialFoo> v2 = v1; assert(NonTrivialFoo::copy_count == 3); // 确认每个元素都触发了拷贝构造 }
- 可平凡复制类型:使用默认拷贝构造的结构体,由于拷贝构造是平凡的,若容器使用memcpy优化,不会执行任何用户逻辑(计数不会变化)。同时验证拷贝后的内容与原容器完全一致:
struct TrivialFoo { int val; static std::atomic<int> copy_count; // 使用默认拷贝构造,确保std::is_trivially_copyable_v<TrivialFoo>为true }; std::atomic<int> TrivialFoo::copy_count = 0; void test_trivial_copy() { my_vector<TrivialFoo> v1{TrivialFoo{1}, TrivialFoo{2}, TrivialFoo{3}}; TrivialFoo::copy_count = 0; my_vector<TrivialFoo> v2 = v1; // 验证内容一致 for (size_t i = 0; i < v1.size(); ++i) { assert(v1[i].val == v2[i].val); } // 确认拷贝构造未被调用(平凡拷贝构造无用户逻辑,计数保持0) assert(TrivialFoo::copy_count == 0); }
方法2:调试器断点跟踪
直接在调试器中给TrivialFoo的拷贝构造函数设置断点:
- 若容器使用memcpy优化,断点不会触发;
- 若容器逐个调用拷贝构造,断点会触发N次(N为容器元素个数)。
方法3:编译期静态断言验证逻辑分支
在容器的拷贝构造函数中,通过if constexpr区分平凡/非平凡类型的处理逻辑,同时添加嵌套类型特征供测试断言:
#include <type_traits> #include <cstdlib> #include <cstring> template <typename T> class my_vector { public: // 暴露编译期特性:是否使用memcpy优化 using uses_memcpy_for_copy = std::bool_constant<std::is_trivially_copyable_v<T>>; my_vector(const my_vector& other) { if constexpr (std::is_trivially_copyable_v<T>) { // memcpy优化路径 data_ = allocate(other.size_); std::memcpy(data_, other.data_, other.size_ * sizeof(T)); size_ = other.size_; capacity_ = other.capacity_; } else { // 逐个拷贝构造路径 data_ = allocate(other.size_); size_ = other.size_; capacity_ = other.capacity_; for (size_t i = 0; i < size_; ++i) { new (data_ + i) T(other.data_[i]); } } } // 其他成员函数:size()、operator[]等 size_t size() const { return size_; } T& operator[](size_t idx) { return data_[idx]; } const T& operator[](size_t idx) const { return data_[idx]; } private: T* data_ = nullptr; size_t size_ = 0; size_t capacity_ = 0; T* allocate(size_t n) { return static_cast<T*>(std::malloc(n * sizeof(T))); } };
然后在单元测试中添加静态断言,确保优化逻辑正确触发:
static_assert(std::is_trivially_copyable_v<TrivialFoo>); static_assert(my_vector<TrivialFoo>::uses_memcpy_for_copy::value); static_assert(!std::is_trivially_copyable_v<NonTrivialFoo>); static_assert(!my_vector<NonTrivialFoo>::uses_memcpy_for_copy::value);
方法4:编译器插桩统计调用
使用GCC/Clang的-finstrument-functions编译选项,为函数调用插桩统计:
- 编写插桩函数,记录
TrivialFoo拷贝构造函数的调用次数; - 编译测试代码并运行,若调用次数为0,则说明容器使用了memcpy优化。
内容的提问来源于stack exchange,提问作者Voivoid
相关产品推荐
相关产品推荐

