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

时间:2014-05-24 17:47:45

标签: c++ inheritance c++11

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那样轻松操纵字符串的能力。但我想我可以创建一个免费的功能来处理它。此外,人们还与操作员<<一起使用来执行格式化插入。


要收集:

  • 我的解决方案是不好的练习(或最差),或者它是C ++习语的例外,它可以吗?
  • 如果不好,还有哪些可行的选择? createString好吗?可以改进吗?

2 个答案:

答案 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课程没问题,因为似乎没有任何情况下正常使用它会让你陷入困境。即便如此,有很多替代方案可能更适合这种情况。

  1. 使用预先存在的库,例如Boost.Format,而不是滚动自己的库。这具有广为人知,测试,支持等优点......

  2. someKindOfError写为可变参数模板,以匹配C版本,但添加了C​​ ++类型 - 安全性。这样做的好处是可以匹配C版本,因此熟悉现有用户。

  3. 授予StringStream conversion operator或明确的to_string功能,而不是继承std::string。这使您可以更灵活地在稍后阶段更改StringStream的实现。 (例如,在稍后阶段,您可能决定要使用某种缓存或缓冲方案,但如果您不确切知道何时从StringStream中提取最终字符串,则这是不可能的。 )。

    实际上,您的设计在概念上存在缺陷。您唯一需要的是能够将StringStream转换为std::string。与使用转换运算符相比,继承是实现该目标的过度严厉方式。

  4. 将您的原始stringstream代码写为一行代码:

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

    ......好吧,那很难看,也许不是。你应该考虑一下,如果你不这样做的唯一原因是你认为这是不可能的。