这是非多态继承的好理由吗?

Is this a good reason for non-polymorphic inheritance?

本文关键字:理由 是非 多态 继承      更新时间:2023-10-16

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::stringstreamStringBuilder。重载操作符<<并添加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的,因为似乎没有任何正常使用它可能会让您陷入麻烦的情况。尽管如此,还是有很多可能更适合这种情况的替代方案。

  1. 使用预先存在的库,例如Boost。格式,而不是自己制作。这样做的优点是得到了广泛的了解、测试和支持,等等。

  2. someKindOfError写入可变模板,以匹配C版本,但增加了c++类型安全的优点。这样做的优点是与C版本匹配,因此您的现有用户很熟悉。

  3. StringStream一个转换操作符或显式的to_string函数,而不是从std::string继承。这使您能够更灵活地在以后的阶段更改StringStream的实现。(例如,在稍后的阶段,您可能决定要使用某种缓存或缓冲方案,但如果您不确切地知道何时将从StringStream中提取最终字符串,这将是不可能的)。

    事实上,你的设计在概念上是有缺陷的。您唯一需要的是将StringStream转换为std::string的能力。与使用转换操作符相比,继承是实现该目标的一种过于繁琐的方式。

  4. 将原始stringstream代码写成一行代码:

someKindOfError(static_cast<std::stringstream &>(
    std::stringstream{} << "invalid argument " << i << ", size = " << size).str());

…好吧,那太难看了,所以也许不是。如果你不这样做的唯一原因是你认为这是不可能的,那么你应该考虑一下。