std::string
(大多数 - 如果不是全部 - 标准类)没有任何虚方法,因此使用虚方法创建一个继承类将导致UB(最有可能是析构函数)。 (如果我错了,请纠正我。)
我认为继承没有多态性就可以了,直到我在网上阅读这个主题。
例如,在这个答案中:Why should one not derive from c++ std string class?对这种做法给出了一些论据。主要原因似乎是切片问题,当派生对象传递给函数代替std::string
参数时,它将禁止添加的功能,从而使非多态性不合逻辑。如果想要扩展string
的功能,那么惯用的C ++方法就是创建自由函数。我同意这一切,特别是因为我是自由职能的倡导者,而不是单一的阶级。
话虽如此,我认为我发现了一种我认为实际上保证来自std::string
的非多态继承的情况。我将首先展示我要解决的问题,然后我将说明为什么我认为继承std::string
是最佳解决方案的原因。
当将用于调试目的的函数从C语言移植到C ++时,我意识到无法在C ++中创建格式化字符串(不使用类似C语言的字符串函数,例如sprintf
)。即:
C版:void someKindOfError(const char *format, ...);
这将被称为:
someKindOfError("invalid argument %d, size = %d", i, size);
C ++版本:void someKindOfError(const std::string &message);
称之为类似的效果将是:
std::stringstream ss;
ss << "invalid argument " << i << ", size = " << size;
someKindOfError(ss.str());
这不能是单行,因为<<
运算符返回ostream
。所以这需要2个额外的行和一个额外的变量。
我提出的解决方案是一个名为StreamString
的类,它继承自std::string
(实际上是一个继承自BasicStreamString
的模板basic_string<>
,但并不重要)并且就新功能而言,它具有运算符<<
,它模仿stringstream
运算符的行为以及string
之间的转换。
所以前面的例子可以变成:
someKindOfError(StreamString() << "invalid argument " << i << ", size = " << size);
请记住参数类型仍为const std::string &
该类已创建且功能齐全。我发现这个类在很多地方非常有用,因为需要特别创建一个字符串,而不必承担额外的stringstream
变量的额外负担。此对象可以像stringstream
一样进一步操作,但它实际上是string
,可以传递给期望string
的函数。
为什么我认为这是C ++习语的例外:
string
的函数时,对象需要与string
完全相同,因此切片问题不是问题。operator <<
我不愿意作为标准 string
对象的自由函数重载(这将在一个库)。我能想到的另一个选择是创建一个可变模板自由函数。类似的东西:
template <class... Args>
std::string createString(Args... args);
允许我们像这样打电话:
someKindOfError(createString("invalid argument ", i , ", size = " , size));
这种替代方案的一个缺点是在创建之后失去了像stringstream
那样轻松操纵字符串的能力。但我想我可以创建一个免费的功能来处理它。此外,人们还与操作员<<
一起使用来执行格式化插入。
要收集:
createString
好吗?可以改进吗?答案 0 :(得分:4)
您不需要为此std::string
派生一个班级。只需创建一个不相关的类,说StringBuilder
在内部保留std::stringstream
。过载运算符&lt;&lt;对于此类并添加std::string
强制转换运算符。
这样的事情应该可以解决问题(未经测试):
class StringBuilder
{
std::ostringstream oss;
public:
operator std::string() const
{
return oss.str();
}
template <class T>
friend StringBuilder& operator <<(StringBuilder& sb, const T& t)
{
sb.oss << t;
return *this;
}
};
答案 1 :(得分:1)
你的StreamString
课程没问题,因为似乎没有任何情况下正常使用它会让你陷入困境。即便如此,有很多替代方案可能更适合这种情况。
使用预先存在的库,例如Boost.Format,而不是滚动自己的库。这具有广为人知,测试,支持等优点......
将someKindOfError
写为可变参数模板,以匹配C版本,但添加了C ++类型 - 安全性。这样做的好处是可以匹配C
版本,因此熟悉现有用户。
授予StringStream
conversion operator或明确的to_string
功能,而不是继承std::string
。这使您可以更灵活地在稍后阶段更改StringStream
的实现。 (例如,在稍后阶段,您可能决定要使用某种缓存或缓冲方案,但如果您不确切知道何时从StringStream
中提取最终字符串,则这是不可能的。 )。
实际上,您的设计在概念上存在缺陷。您唯一需要的是能够将StringStream
转换为std::string
。与使用转换运算符相比,继承是实现该目标的过度严厉方式。
将您的原始stringstream
代码写为一行代码:
someKindOfError(static_cast<std::stringstream &>(
std::stringstream{} << "invalid argument " << i << ", size = " << size).str());
......好吧,那很难看,也许不是。你应该考虑一下,如果你不这样做的唯一原因是你认为这是不可能的。