C++ Zip类临时对象生命周期异常问题及修复咨询
C++ Zip类处理临时容器的生命周期异常问题
异常现象
运行代码后出现如下错误输出:
1 | 11 2 | 22 3 | 33 1 | 11 | 0 <--- 异常 2 | 22 | 6.91092e-317 <--- 异常 3 | 33 | 9
第二个for循环中,传入的临时对象std::vector<double>{7, 8, 9}在Zip构造函数退出后就被销毁,导致后续访问时输出错误值。
疑问
Zip类内部以const Ts&(const引用)存储元素,按C++规则,临时对象绑定到const引用时生命周期应延长至引用所在对象销毁(即for循环结束后),为何此处未生效?该如何修复?
原代码
#include <tuple> #include <vector> #include <iostream> template<class... Ts> class Zip { public: explicit Zip(const Ts&... objs) : m_data(objs...) { } struct ZipIterator { public: explicit ZipIterator(const std::tuple<const Ts&...>& data, std::size_t idx) : m_data(data), m_idx(idx) { } ZipIterator& operator++() { ++m_idx; return *this; } bool operator!=(const ZipIterator& rhs) const { return m_idx != rhs.m_idx; } auto operator*() const { return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.at(m_idx)...); }, m_data); } private: const std::tuple<const Ts&...>& m_data; std::size_t m_idx; }; ZipIterator begin() const { return ZipIterator(m_data, 0); } ZipIterator end() const { return ZipIterator(m_data, std::get<0>(m_data).size()); } private: std::tuple<const Ts&...> m_data; }; int main() { const std::vector<double> vec1{1, 2, 3}; const std::vector<double> vec2{11, 22, 33}; for (const auto& [v1, v2] : Zip(vec1, vec2)) { std::cout << v1 << " | " << v2 << std::endl; } std::cout << std::endl; for (const auto& [v1, v2, v3] : Zip(vec1, vec2, std::vector<double>{7, 8, 9})) { std::cout << v1 << " | " << v2 << " | " << v3 << std::endl; } return 0; }
问题原因
C++中,临时对象绑定到函数参数的const引用时,生命周期会延长至该函数作用域结束,但你的代码中,是将这些引用存储到了std::tuple<const Ts&...>成员中:
- 当用临时对象初始化tuple的引用成员时,临时对象的生命周期仅延长到tuple构造完成的瞬间,而非
Zip对象的整个生命周期。 - tuple本身只是存储引用,不会接管临时对象的所有权,因此
Zip构造函数执行完毕后,临时对象立即被销毁,后续通过引用访问就是未定义行为。
修复方案
让Zip类区分左值和右值参数:对左值存储引用,对右值(临时对象)存储值,从而保证临时对象的生命周期与Zip对象一致。
修改后的完整代码
#include <tuple> #include <vector> #include <iostream> #include <type_traits> template<class... Ts> class Zip { // 对左值参数保留引用,对右值参数存储值类型 using DataTuple = std::tuple<std::conditional_t<std::is_lvalue_reference_v<Ts>, Ts, std::decay_t<Ts>>...>; public: // 完美转发参数,适配左值和右值 explicit Zip(Ts&&... objs) : m_data(std::forward<Ts>(objs)...) {} struct ZipIterator { public: explicit ZipIterator(const DataTuple& data, std::size_t idx) : m_data(data), m_idx(idx) {} ZipIterator& operator++() { ++m_idx; return *this; } bool operator!=(const ZipIterator& rhs) const { return m_idx != rhs.m_idx; } auto operator*() const { return std::apply([this](auto const&... obj) { return std::forward_as_tuple(obj.at(m_idx)...); }, m_data); } private: const DataTuple& m_data; std::size_t m_idx; }; ZipIterator begin() const { return ZipIterator(m_data, 0); } ZipIterator end() const { return ZipIterator(m_data, std::get<0>(m_data).size()); } private: DataTuple m_data; }; int main() { const std::vector<double> vec1{1, 2, 3}; const std::vector<double> vec2{11, 22, 33}; for (const auto& [v1, v2] : Zip(vec1, vec2)) { std::cout << v1 << " | " << v2 << std::endl; } std::cout << std::endl; for (const auto& [v1, v2, v3] : Zip(vec1, vec2, std::vector<double>{7, 8, 9})) { std::cout << v1 << " | " << v2 << " | " << v3 << std::endl; } return 0; }
关键修改点
- 类型别名
DataTuple:用std::conditional_t判断每个参数类型,如果是左值引用则保留引用,否则使用std::decay_t得到值类型,确保临时对象被存储为值而非引用。 - 构造函数完美转发:使用
Ts&&和std::forward,既可以绑定左值(传递引用),也可以接收右值(移动或拷贝到tuple中)。 - 迭代器适配:迭代器中的
m_data改为引用新的DataTuple类型,保证访问的有效性。
修改后,临时容器会被存储为Zip类的成员,生命周期与Zip对象一致,避免了悬垂引用问题。
内容的提问来源于stack exchange,提问作者Phil-ZXX
相关产品推荐
相关产品推荐

