这是非多态继承的好理由吗?
Is this a good reason for non-polymorphic inheritance?
std::string
(大多数——如果不是全部——标准类)没有任何虚方法,因此创建一个带有虚方法的继承类将导致UB(很可能是析构函数造成的)。(如果我说错了请指正)。
我认为没有多态性的继承是可以的,直到我在网上读到这个主题。
例如,在这个答案中:为什么不能从c++的std string类派生?对这种做法提出了一些反对意见。主要原因似乎是切片问题,当将派生对象传递给代替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
。所以这需要额外的两行和一个额外的变量。
我想出的解决方案是一个名为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
一样轻松操作字符串的能力。但我想我也可以创建一个自由函数来处理它。也可以使用<<
操作符来执行格式化的插入。
四舍五入:
- 我的解决方案是坏的做法(或最坏的)或它是一个例外的c++风格,它是OK的?
- 如果它是坏的,有什么可行的替代方案?
createString
可以吗?可以改进吗?
您不需要为此从std::string
派生一个类。只要创建一个不相关的类,比如在内部保留std::stringstream
的StringBuilder
。重载操作符<<并添加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;
}
};
您的StreamString
类是OK的,因为似乎没有任何正常使用它可能会让您陷入麻烦的情况。尽管如此,还是有很多可能更适合这种情况的替代方案。
-
使用预先存在的库,例如Boost。格式,而不是自己制作。这样做的优点是得到了广泛的了解、测试和支持,等等。
-
将
someKindOfError
写入可变模板,以匹配C版本,但增加了c++类型安全的优点。这样做的优点是与C
版本匹配,因此您的现有用户很熟悉。 -
给
StringStream
一个转换操作符或显式的to_string
函数,而不是从std::string
继承。这使您能够更灵活地在以后的阶段更改StringStream
的实现。(例如,在稍后的阶段,您可能决定要使用某种缓存或缓冲方案,但如果您不确切地知道何时将从StringStream
中提取最终字符串,这将是不可能的)。事实上,你的设计在概念上是有缺陷的。您唯一需要的是将
StringStream
转换为std::string
的能力。与使用转换操作符相比,继承是实现该目标的一种过于繁琐的方式。 -
将原始
stringstream
代码写成一行代码:
someKindOfError(static_cast<std::stringstream &>(
std::stringstream{} << "invalid argument " << i << ", size = " << size).str());
…好吧,那太难看了,所以也许不是。如果你不这样做的唯一原因是你认为这是不可能的,那么你应该考虑一下。
- 有充分的理由在h文件中使用include保护而不是cpp文件吗
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 为什么 std::optional::operator=(U&&) 要求你是非标量类型?
- 是否可以在C++中有一个"generic"模板参数,该参数可以是非类型模板参数或类型?
- NVCC 错误:string_view.h:constexpr 函数返回是非常量
- 编译器是否必须始终删除 try-catch 块(如果它被证明是非抛出的)
- C++ 电话号码字母拨号程序 - 语法错别字?还是非 ASCII 字符?
- 在调用其析构函数之前,是否有任何实际理由检查某些东西是否可破坏?
- 什么是非营利组织???我的问题是我不明白为什么有人会使用它
- 有什么理由不扩展 std::set 以添加下标运算符吗?
- 是否有理由在标题中保留完全专用的模板?
- 为什么带有指针子对象的文字类类型的 constexpr 表达式不能是非类型模板参数
- 任何不使用全局Lambda的理由
- 错误:请求成员 .. 是非类类型"char"
- 如果我有很多具有相似前缀的字符串,是否有理由从该前缀创建一个子字符串?
- 具有定义模板还是非模板的友元函数
- 如果代码中没有连接任何插槽,是否有理由发出Qt信号?
- 为什么在C++中,内建赋值返回的是非常量引用而不是常量引用
- 请求成员 'begin' in 'c' 中,它是非类类型 'char [101]' sort(c.begin(), c
- 这是非多态继承的好理由吗?