如何实现Observed类?使用std::invoke重定向函数调用遇问题求助
解决Observed类的内部对象修改追踪问题
问题概述
设计Observed<T>类用于存储T类型对象,并通过m_updated标记追踪对象自上次读取后是否被修改。初始实现存在缺陷:当调用value()返回内部对象的引用后,直接修改对象(例如对std::vector<int>调用push_back)会绕过write()方法,导致m_updated无法被正确标记为true。尝试添加call()方法来包裹修改操作,但调用std::vector等带模板参数的类的成员函数指针时遇到困难。
解决方案
方案1:使用代理类自动追踪修改
不直接返回原始对象的引用,而是返回一个代理对象,所有通过代理对内部对象的操作都会自动触发m_updated标记。这种方式用法更自然,无需手动处理成员函数指针:
#include <concepts> #include <utility> template <std::copyable T> class Observed { public: Observed() : m_data{}, m_updated{false} {}; Observed(const T& data) : m_data{data}, m_updated{true} {}; // 移动、拷贝构造/赋值函数(按需实现) Observed(Observed&&) = default; Observed& operator=(Observed&&) = default; Observed(const Observed&) = default; Observed& operator=(const Observed&) = default; // 返回代理对象,用于修改内部数据 class ModifyProxy { public: ModifyProxy(T& data, bool& updated) : m_data(data), m_updated(updated) {} // 重载->运算符,指向内部对象并标记修改 T* operator->() { m_updated = true; return &m_data; } // 重载*运算符,返回内部对象引用并标记修改 T& operator*() { m_updated = true; return m_data; } private: T& m_data; bool& m_updated; }; ModifyProxy modify() { return ModifyProxy{m_data, m_updated}; } // 只读访问,不修改标记 const T& value() const { return m_data; } // 读取后重置更新标记 const T& read_and_reset() { m_updated = false; return m_data; } bool modified() const { return m_updated; } template <typename U> requires(std::same_as<T, std::remove_cvref_t<U>>) void write(U&& value) { m_data = std::forward<U>(value); m_updated = true; } private: T m_data; bool m_updated; };
使用示例:
#include <vector> #include <cassert> int main() { Observed<std::vector<int>> v; v.modify()->push_back(1); assert(v.modified() == true); const auto& vec = v.read_and_reset(); assert(vec.size() == 1); assert(v.modified() == false); v.write(std::vector<int>{2,3}); assert(v.modified() == true); }
方案2:修复call()函数实现
如果坚持使用call()方法包裹修改操作,需要修正函数的返回值类型推导,并正确处理成员函数指针的重载问题:
#include <concepts> #include <utility> #include <functional> template <std::copyable T> class Observed { public: Observed() : m_data{}, m_updated{false} {}; Observed(const T& data) : m_data{data}, m_updated{true} {}; // 移动、拷贝构造/赋值函数(按需实现) Observed(Observed&&) = default; Observed& operator=(Observed&&) = default; Observed(const Observed&) = default; Observed& operator=(const Observed&) = default; // 修正后的call函数:正确推导invoke的参数类型 template<typename Func, typename... Ts> auto call(Func&& func, Ts&&... args) -> std::invoke_result_t<Func, T&, Ts...> { m_updated = true; return std::invoke(std::forward<Func>(func), m_data, std::forward<Ts>(args)...); } bool modified() const { return m_updated; } const T& value() const { return m_data; } const T& read_and_reset() { m_updated = false; return m_data; } template <typename U> requires(std::same_as<T, std::remove_cvref_t<U>>) void write(U&& value) { m_data = std::forward<U>(value); m_updated = true; } private: T m_data; bool m_updated; };
使用示例:
由于std::vector::push_back存在重载(const引用和右值版本),需要显式指定要调用的重载:
#include <vector> #include <cassert> int main() { Observed<std::vector<int>> v; // 调用const引用版本的push_back v.call(static_cast<void (std::vector<int>::*)(const int&)>(&std::vector<int>::push_back), 1); assert(v.modified() == true); // 调用clear方法 v.call(&std::vector<int>::clear); assert(v.value().empty()); }
方案对比
- 代理类方案:用法直观,自动覆盖所有修改操作,无需关注成员函数的重载或模板参数问题,是更推荐的实现方式。
- call()函数方案:适合需要显式控制修改操作的场景,但使用时需要手动处理成员函数指针的重载问题,相对繁琐。
内容的提问来源于stack exchange,提问作者Jonah F
相关产品推荐
相关产品推荐

