C++11中如何设计支持返回T或const T&的灵活接口?
问题描述
我想要为返回消息的类设计一个接口。问题在于,有时返回string更为合适,有时返回const string&更优。我希望让继承该接口的子类能够灵活选择返回类型,而非定义两个重复的纯虚函数:
struct Verbose{ // 消息为类成员时的接口 virtual const string& getMsg()const=0; // 消息在函数内创建时的接口 virtual string getMsg()const=0; };
我期望使用如下形式的统一接口来实现这种灵活性:
struct Verbose{ virtual myPrintableType getMsg()const=0; };
请问实现这一需求的最佳方式是什么?
可行解决方案
方案一:使用std::string_view作为统一返回类型
std::string_view(C++17及以上支持)是对字符串的轻量级非拥有式视图,能同时兼容const std::string&和临时std::string的场景:
- 当子类消息是类成员时,直接返回
std::string_view(member_str); - 当子类需要返回临时创建的字符串时,可将临时字符串存入类内的
mutable std::string缓存,再返回其string_view(避免临时对象生命周期问题)。
示例代码:
#include <string> #include <string_view> struct Verbose{ virtual std::string_view getMsg() const = 0; }; // 子类1:消息为类成员,返回引用的视图 struct MemberVerbose : Verbose { private: std::string msg = "I'm a member string"; public: std::string_view getMsg() const override { return msg; } }; // 子类2:消息临时创建,用mutable缓存存储 struct TempVerbose : Verbose { private: mutable std::string temp_cache; public: std::string_view getMsg() const override { temp_cache = "I'm a temporarily created string"; return temp_cache; } };
方案二:自定义可隐式转换的包装类型
如果需要兼容C++17之前的版本,可以自定义包装类,让它同时接受const std::string&和临时std::string,并支持隐式转换到std::string或std::string_view:
#include <string> class PrintableString { private: const std::string* ref_str = nullptr; std::string owned_str; public: // 接受const引用的构造函数 PrintableString(const std::string& s) : ref_str(&s) {} // 接受临时字符串的构造函数(移动语义) PrintableString(std::string&& s) : owned_str(std::move(s)) {} // 转换为string_view,方便读取内容 operator std::string_view() const { return ref_str ? *ref_str : owned_str; } // 转换为string,当需要拷贝时使用 operator std::string() const { return ref_str ? *ref_str : owned_str; } }; struct Verbose{ virtual PrintableString getMsg() const = 0; }; struct MemberVerbose : Verbose { private: std::string msg = "Member message"; public: PrintableString getMsg() const override { return msg; // 调用const引用构造 } }; struct TempVerbose : Verbose { public: PrintableString getMsg() const override { return std::string("Temporary message"); // 调用移动构造 } };
方案三:返回std::variant<const std::string&, std::string>(C++17+)
这种方式明确表示返回值可能是引用或值,但调用方需要处理变体的两种情况,使用起来稍显繁琐:
#include <string> #include <variant> struct Verbose{ virtual std::variant<const std::string&, std::string> getMsg() const = 0; }; struct MemberVerbose : Verbose { private: std::string msg = "Member string"; public: std::variant<const std::string&, std::string> getMsg() const override { return std::cref(msg); // 包装为const引用 } }; struct TempVerbose : Verbose { public: std::variant<const std::string&, std::string> getMsg() const override { return std::string("Temporary string"); } }; // 调用方使用示例 void useVerbose(const Verbose& v) { auto msg_var = v.getMsg(); if (auto* ref = std::get_if<const std::string&>(&msg_var)) { // 处理引用情况 } else { auto str = std::get<std::string>(msg_var); // 处理值情况 } }
方案选择建议
- 优先选方案一:
std::string_view是标准库提供的轻量级解决方案,代码简洁、性能开销小,只需注意临时字符串的生命周期问题; - 若需兼容C++17之前的环境,选方案二的自定义包装类;
- 方案三的变体类型适合需要明确区分两种返回场景的情况,但调用方代码更复杂,一般不推荐作为首选。
内容的提问来源于stack exchange,提问作者Jerome Demantke
相关产品推荐
相关产品推荐

