如何避免结构体单个成员元素被复制或移动?同时实现结构体参数的双方式访问(元素名+字符串)
如何避免结构体单个成员元素被复制或移动?同时实现结构体参数的双方式访问(元素名+字符串)
嘿,我完全懂你要的这种需求——既要像普通结构体那样用成员名直接撸参数,又能通过字符串索引访问,还得防着别人随便复制/移动单个成员对吧?之前做配置管理模块的时候我也折腾过这个,踩了几个坑后整理出两个靠谱的实现思路,看你对“禁止复制/移动”的严格程度选:
轻量级方案:防意外移动+限制直接复制
如果你的需求只是防止成员被意外移动(比如不小心用std::move把成员掏空),同时不想让外部直接拿到成员的可修改引用,那这个方案足够用了,代码也简洁:
#include <string> #include <unordered_map> #include <stdexcept> #include <iostream> struct Config { public: // 用成员名直接访问:只读返回const引用,修改用专门的set方法 const std::string& paramThis() const { return paramThis_; } void setParamThis(const std::string& val) { paramThis_ = val; } void setParamThis(std::string&& val) { paramThis_ = std::move(val); } const std::string& paramThat() const { return paramThat_; } void setParamThat(const std::string& val) { paramThat_ = val; } void setParamThat(std::string&& val) { paramThat_ = std::move(val); } // 用字符串访问的接口 const std::string& get(const std::string& key) const { auto it = const_member_map.find(key); if (it == const_member_map.end()) { throw std::invalid_argument("未知参数键:" + key); } return *(it->second); } void set(const std::string& key, const std::string& val) { auto it = mutable_member_map.find(key); if (it == mutable_member_map.end()) { throw std::invalid_argument("未知参数键:" + key); } *(it->second) = val; } void set(const std::string& key, std::string&& val) { auto it = mutable_member_map.find(key); if (it == mutable_member_map.end()) { throw std::invalid_argument("未知参数键:" + key); } *(it->second) = std::move(val); } private: // 把成员设为私有,外部不能直接访问 std::string paramThis_; std::string paramThat_; // 静态映射表:字符串到成员指针,只用初始化一次,省内存 static const std::unordered_map<std::string, const std::string* const> const_member_map; static const std::unordered_map<std::string, std::string* const> mutable_member_map; }; // 类外初始化映射表,加新参数时这里也要同步加哦 const std::unordered_map<std::string, const std::string* const> Config::const_member_map = { {"paramThis", &Config::paramThis_}, {"paramThat", &Config::paramThat_} }; const std::unordered_map<std::string, std::string* const> Config::mutable_member_map = { {"paramThis", &Config::paramThis_}, {"paramThat", &Config::paramThat_} };
这个方案好在哪?
- 外部没法直接写
auto str = std::move(config.paramThis_);这种坑代码,因为成员是私有;getter返回的是const引用,std::move也没法作用在const引用上,彻底防意外移动。 - 虽然用户可以用
auto str = config.paramThis();来拷贝字符串,但这是显式的拷贝,一般不会是意外操作,要是你能接受这种显式拷贝,那这个方案完全够用。 - 双访问方式都支持,代码量不大,维护起来也简单,加新参数只要三步:加私有成员、加对应的get/set方法、更新映射表。
硬核方案:彻底禁止单个成员的复制/移动
要是你要完全杜绝任何形式的单个成员复制/移动,哪怕是用户显式拷贝成员对象也不行,那可以把成员封装成一个禁用拷贝的内部类:
#include <string> #include <unordered_map> #include <stdexcept> #include <iostream> struct Config { private: // 内部封装一个锁死拷贝/移动的字符串类 class LockedString { public: LockedString() = default; explicit LockedString(const std::string& val) : str_(val) {} explicit LockedString(std::string&& val) : str_(std::move(val)) {} // 直接禁用拷贝和移动构造/赋值,从根上防复制 LockedString(const LockedString&) = delete; LockedString& operator=(const LockedString&) = delete; LockedString(LockedString&&) = delete; LockedString& operator=(LockedString&&) = delete; // 只提供只读访问和受控的修改接口 const std::string& get() const { return str_; } void set(const std::string& val) { str_ = val; } void set(std::string&& val) { str_ = std::move(val); } private: std::string str_; }; // 用LockedString代替普通std::string做成员 LockedString paramThis_; LockedString paramThat_; public: // 成员名访问接口,只能读或通过set方法改 const std::string& paramThis() const { return paramThis_.get(); } void setParamThis(const std::string& val) { paramThis_.set(val); } void setParamThis(std::string&& val) { paramThis_.set(std::move(val)); } const std::string& paramThat() const { return paramThat_.get(); } void setParamThat(const std::string& val) { paramThat_.set(val); } void setParamThat(std::string&& val) { paramThat_.set(std::move(val)); } // 字符串访问接口 const std::string& get(const std::string& key) const { static const std::unordered_map<std::string, const LockedString* const> const_map = { {"paramThis", ¶mThis_}, {"paramThat", ¶mThat_} }; auto it = const_map.find(key); if (it == const_map.end()) { throw std::invalid_argument("未知参数键:" + key); } return it->second->get(); } void set(const std::string& key, const std::string& val) { static const std::unordered_map<std::string, LockedString* const> mutable_map = { {"paramThis", ¶mThis_}, {"paramThat", ¶mThat_} }; auto it = mutable_map.find(key); if (it == mutable_map.end()) { throw std::invalid_argument("未知参数键:" + key); } it->second->set(val); } void set(const std::string& key, std::string&& val) { static const std::unordered_map<std::string, LockedString* const> mutable_map = { {"paramThis", ¶mThis_}, {"paramThat", ¶mThat_} }; auto it = mutable_map.find(key); if (it == mutable_map.end()) { throw std::invalid_argument("未知参数键:" + key); } it->second->set(std::move(val)); } };
这个方案好在哪?
- 不管是意外还是故意,外部都没法复制或移动单个成员对象,
auto badCopy = cfg.paramThis_;这种代码直接编译报错,因为成员是私有且LockedString禁用了拷贝。 - 完全掌控成员的修改权限,所有修改都必须通过我们提供的set方法,方便加日志、校验逻辑之类的扩展。
最后给你个使用示例,看看实际怎么用:
int main() { Config cfg; // 成员名方式操作参数 cfg.setParamThis("初始配置A"); cfg.setParamThat(std::string("初始配置B")); std::cout << "成员名访问:" << cfg.paramThis() << " | " << cfg.paramThat() << std::endl; // 字符串方式操作参数 cfg.set("paramThis", "修改后配置A"); cfg.set("paramThat", std::string("修改后配置B")); std::cout << "字符串访问:" << cfg.get("paramThis") << " | " << cfg.get("paramThat") << std::endl; // 下面这些坑代码都会编译报错,完美防踩坑: // auto badMove = std::move(cfg.paramThis_); // 私有成员+禁用移动,编译失败 // auto badCopy = cfg.paramThis_; // 私有成员+禁用拷贝,编译失败 // cfg.paramThis() = "直接赋值"; // get返回const引用,不能赋值,编译失败 return 0; }
你可以根据自己的需求选对应的方案,我自己做项目的时候,大部分情况用轻量级方案就够了,硬核方案适合那种参数绝对不能被随便拷贝的场景,比如敏感配置项。
内容来源于stack exchange
相关产品推荐
相关产品推荐

