C++14技术问题:编译时常量参数调用模板函数的宏及memcpy特化
解决方案:宏+模板特化实现编译期常量长度的memcpy优化
刚好在C++14下做过类似的memcpy优化需求,核心思路是通过宏区分编译期常量和运行期变量,配合模板特化把固定长度的拷贝替换成更高效的整数赋值操作。下面是完整的实现步骤和细节:
第一步:实现特化的模板拷贝函数
首先我们定义mymemcpy模板,针对你需要的2、4、8、16字节长度做特化,直接用整数类型赋值替代字节拷贝:
#include <cstdint> #include <cstring> // 如果需要16字节的SIMD优化,引入对应头文件(可选) // #include <emmintrin.h> // 通用模板:默认调用标准memcpy(处理非指定长度的情况) template <size_t N> void mymemcpy(void* dst, const void* src) { std::memcpy(dst, src, N); } // 2字节特化:用uint16_t直接赋值 template <> void mymemcpy<2>(void* dst, const void* src) { *static_cast<uint16_t*>(dst) = *static_cast<const uint16_t*>(src); } // 4字节特化:用uint32_t直接赋值 template <> void mymemcpy<4>(void* dst, const void* src) { *static_cast<uint32_t*>(dst) = *static_cast<const uint32_t*>(src); } // 8字节特化:用uint64_t直接赋值 template <> void mymemcpy<8>(void* dst, const void* src) { *static_cast<uint64_t*>(dst) = *static_cast<const uint64_t*>(src); } // 16字节特化:可选两种实现 // 方案1:通用双64位赋值(兼容所有编译器) template <> void mymemcpy<16>(void* dst, const void* src) { auto dst_ptr = static_cast<uint64_t*>(dst); auto src_ptr = static_cast<const uint64_t*>(src); dst_ptr[0] = src_ptr[0]; dst_ptr[1] = src_ptr[1]; } // 方案2:SIMD优化(需要编译器支持SSE2,处理未对齐更高效) // template <> // void mymemcpy<16>(void* dst, const void* src) { // _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), // _mm_loadu_si128(reinterpret_cast<const __m128i*>(src))); // }
第二步:定义编译期常量判断宏
C++14没有if constexpr,所以我们需要依赖编译器内置函数来判断参数是否是编译期常量,这里做了主流编译器的兼容:
// 兼容GCC/Clang的常量判断 #ifdef __GNUC__ #define IS_COMPILE_TIME_CONSTANT(expr) __builtin_constant_p(expr) // 兼容MSVC的常量判断 #elif _MSC_VER #define IS_COMPILE_TIME_CONSTANT(expr) __is_constant_expression(expr) // 其他编译器默认按运行期变量处理 #else #define IS_COMPILE_TIME_CONSTANT(expr) false #endif
第三步:替换memcpy的宏
最后我们定义替换memcpy的宏,根据长度是否是编译期常量且为指定值,自动选择调用特化模板还是标准memcpy:
#define memcpy(dst, src, len) \ do { \ if (IS_COMPILE_TIME_CONSTANT(len) && ((len) == 2 || (len) == 4 || (len) == 8 || (len) == 16)) { \ mymemcpy<static_cast<size_t>(len)>(dst, src); \ } else { \ std::memcpy(dst, src, len); \ } \ } while(0)
测试示例
现在你可以像平常一样使用memcpy,宏会自动处理两种情况:
// 编译期常量长度:会被替换成mymemcpy<4>(a, b) char a[4], b[4] = {1, 2, 3, 4}; memcpy(a, b, 4); // 运行期变量长度:保持调用标准std::memcpy int c = 7 + 1; memcpy(a, b, c);
注意事项
- 对齐问题:整数直接赋值要求内存地址是对齐的(比如4字节赋值要求地址是4的倍数),如果你的内存可能未对齐,建议用16字节特化的SIMD方案(
_mm_storeu_si128支持未对齐),或者放弃对应长度的优化。 - 避免宏冲突:如果需要保留某些标准
memcpy调用,直接使用全名std::memcpy即可,不会被宏替换。 - 编译器兼容性:上面的常量判断宏覆盖了GCC、Clang、MSVC,其他小众编译器可能需要调整。
内容的提问来源于stack exchange,提问作者JeffW
相关产品推荐
相关产品推荐

