C++如何无需拷贝高效返回内部非const指针的常量视图
问题说明
类内部需要存储non-const指针std::vector<char*>用于修改指向的对象,对外需要提供指针的常量视图,直接返回std::vector<const char*>会触发类型不匹配的编译错误,逐元素拷贝构造返回值在百万级数据量下会产生不必要的性能开销。
原问题中给出的错误示例代码如下:
#include <vector> #include <iostream> class example { public: example() { bytePtrs_.push_back(new char); *bytePtrs_[0] = '$'; } // 无法通过编译:std::vector<char*>和std::vector<const char*>是完全独立的类型,无隐式转换规则 // std::vector<const char*> getPtrs() { // return bytePtrs_; // } // 存在全量拷贝开销的实现 std::vector<const char*> getPtrs() { std::vector<const char*> ret; for (auto &ptr : bytePtrs_) ret.push_back(ptr); return ret; } private: std::vector<char*> bytePtrs_; }; int main() { example e; std::vector<const char*> bytePtrs = e.getPtrs(); std::cout << bytePtrs[0] << std::endl; }
零拷贝实现方案
方案1:使用std::span返回只读视图(C++20及以上推荐)
C++20引入的std::span是轻量的非持有视图,支持容器元素类型的合法限定转换,你只需要将返回值改为std::span<const char* const>即可,完全不需要拷贝任何元素:
- 视图的元素类型是
const char* const:顶层const保证外部无法修改视图中存储的指针值,底层const保证外部无法通过指针修改指向的字符内容,完全满足常量视图的要求 - span内部仅持有原始内存的指针和元素个数,构造开销是O(1),和数据量完全无关
修改后的代码:
#include <vector> #include <iostream> #include <span> // C++20头文件 class example { public: example() { bytePtrs_.push_back(new char); *bytePtrs_[0] = '$'; } // 零拷贝返回常量视图,编译通过 std::span<const char* const> getPtrs() { return bytePtrs_; } private: std::vector<char*> bytePtrs_; }; int main() { example e; auto bytePtrs = e.getPtrs(); // 以下操作都会编译失败,符合常量视图的约束 // bytePtrs[0] = nullptr; // 无法修改指针本身 // *bytePtrs[0] = 'a'; // 无法修改指针指向的内容 std::cout << bytePtrs[0] << std::endl; }
方案2:自定义轻量视图(兼容C++11及以上版本)
如果编译环境不支持C++20,可以自行实现一个极简的随机访问视图类,内部持有原vector的起止指针,解引用时自动做类型转换,同样零拷贝:
#include <vector> #include <iostream> #include <cstddef> class ConstCharPtrView { public: ConstCharPtrView(const char* const* begin, const char* const* end) : begin_(begin), end_(end) {} const char* const& operator[](size_t idx) const { return begin_[idx]; } size_t size() const { return end_ - begin_; } const char* const* begin() const { return begin_; } const char* const* end() const { return end_; } private: const char* const* begin_; const char* const* end_; }; class example { public: example() { bytePtrs_.push_back(new char); *bytePtrs_[0] = '$'; } ConstCharPtrView getPtrs() { return ConstCharPtrView( reinterpret_cast<const char* const*>(bytePtrs_.data()), reinterpret_cast<const char* const*>(bytePtrs_.data() + bytePtrs_.size()) ); } private: std::vector<char*> bytePtrs_; }; int main() { example e; auto bytePtrs = e.getPtrs(); std::cout << bytePtrs[0] << std::endl; }
注意:这里的
reinterpret_cast是安全的,因为char*和const char*具有相同的内存布局和对齐要求,通过转换后的指针读取值是完全符合C++标准的行为。
不推荐的做法
不要直接返回const std::vector<char*>&:外部可以通过const_cast轻松去掉const限定,修改内部存储的指针或者指针指向的内容,完全打破常量约束,存在安全隐患。
内容的提问来源于stack exchange,提问作者CraigDavid
相关产品推荐
相关产品推荐

