为什么不应该从 c++ std 字符串类派生? [英] Why should one not derive from c++ std string class?

查看:23
本文介绍了为什么不应该从 c++ std 字符串类派生?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想问一下 Effective C++ 中提出的一个具体观点.

I wanted to ask about a specific point made in Effective C++.

它说:

如果一个类需要像一个多态类一样运行,那么析构函数应该是虚拟的.它还进一步补充说,由于 std::string 没有虚拟析构函数,因此永远不应从中派生.另外 std::string 甚至都没有被设计为基类,忘记多态基类.

A destructor should be made virtual if a class needs to act like a polymorphic class. It further adds that since std::string does not have a virtual destructor, one should never derive from it. Also std::string is not even designed to be a base class, forget polymorphic base class.

我不明白要成为基类(不是多态类)的类具体需要什么?

I do not understand what specifically is required in a class to be eligible for being a base class (not a polymorphic one)?

我不应该从 std::string 类派生的唯一原因是它没有虚拟析构函数吗?出于可重用性的目的,可以定义一个基类,并且可以从它继承多个派生类.那么是什么让 std::string 甚至没有资格作为基类?

Is the only reason that I should not derive from std::string class is it does not have a virtual destructor? For reusability purpose a base class can be defined and multiple derived class can inherit from it. So what makes std::string not even eligible as a base class?

另外,如果有一个纯粹为了可重用性目的而定义的基类,并且有许多派生类型,有没有办法阻止客户端执行 Base* p = new Derived() 因为类不是要多态使用吗?

Also, if there is a base class purely defined for reusability purpose and there are many derived types, is there any way to prevent client from doing Base* p = new Derived() because the classes are not meant to be used polymorphically?

推荐答案

我认为这个声明反映了这里的混乱(强调我的):

I think this statement reflects the confusion here (emphasis mine):

我不明白在类中有什么具体要求才有资格成为基类(不是多态类)?

I do not understand what specifically is required in a class to be eligible for being a base clas (not a polymorphic one)?

在惯用的 C++ 中,从类派生有两种用途:

In idiomatic C++, there are two uses for deriving from a class:

  • 私有继承,用于使用模板的混合和面向方面的编程.
  • public 继承,仅用于多态情况.编辑:好的,我想这也可以用在一些混合场景中——比如 boost::iterator_facade——当 CRTP 正在使用中.
  • private inheritance, used for mixins and aspect oriented programming using templates.
  • public inheritance, used for polymorphic situations only. EDIT: Okay, I guess this could be used in a few mixin scenarios too -- such as boost::iterator_facade -- which show up when the CRTP is in use.

如果你不想做一些多态的事情,绝对没有理由在 C++ 中公开派生一个类.该语言带有免费函数作为该语言的标准特性,而免费函数正是您在这里应该使用的.

There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic. The language comes with free functions as a standard feature of the language, and free functions are what you should be using here.

这样想——你真的想仅仅因为你想添加一些方法就强迫你的代码的客户端转换为使用一些专有的字符串类吗?因为与 Java 或 C#(或大多数类似的面向对象语言)不同,当您在 C++ 中派生类时,基类的大多数用户都需要了解这种更改.在Java/C#中,类通常是通过引用来访问的,类似于C++的指针.因此,涉及到一定程度的间接性,它将您的类的客户端解耦,允许您在其他客户端不知道的情况下替换派生类.

Think of it this way -- do you really want to force clients of your code to convert to using some proprietary string class simply because you want to tack on a few methods? Because unlike in Java or C# (or most similar object oriented languages), when you derive a class in C++ most users of the base class need to know about that kind of a change. In Java/C#, classes are usually accessed through references, which are similar to C++'s pointers. Therefore, there's a level of indirection involved which decouples the clients of your class, allowing you to substitute a derived class without other clients knowing.

然而,在 C++ 中,类是值类型——与大多数其他面向对象语言不同.看到这一点的最简单方法是所谓的切片问题.基本上,请考虑:

However, in C++, classes are value types -- unlike in most other OO languages. The easiest way to see this is what's known as the slicing problem. Basically, consider:

int StringToNumber(std::string copyMeByValue)
{
    std::istringstream converter(copyMeByValue);
    int result;
    if (converter >> result)
    {
        return result;
    }
    throw std::logic_error("That is not a number.");
}

如果您将自己的字符串传递给此方法,将调用 std::string 的复制构造函数来进行复制,而不是派生对象的复制构造函数 -- 不管 std::string 的子类是什么.这可能会导致您的方法与附加到字符串的任何内容之间出现不一致.函数 StringToNumber 不能简单地接受任何你的派生对象并复制它,仅仅因为你的派生对象可能具有与 std::string 不同的大小——但是这个函数被编译为只为自动存储中的 std::string 保留空间.在 Java 和 C# 中,这不是问题,因为唯一涉及的自动存储是引用类型,并且引用的大小始终相同.在 C++ 中不是这样.

If you pass your own string to this method, the copy constructor for std::string will be called to make a copy, not the copy constructor for your derived object -- no matter what child class of std::string is passed. This can lead to inconsistency between your methods and anything attached to the string. The function StringToNumber cannot simply take whatever your derived object is and copy that, simply because your derived object probably has a different size than a std::string -- but this function was compiled to reserve only the space for a std::string in automatic storage. In Java and C# this is not a problem because the only thing like automatic storage involved are reference types, and the references are always the same size. Not so in C++.

长话短说——不要使用继承来添加 C++ 中的方法.这不是惯用的,会导致语言出现问题.尽可能使用非友元、非成员函数,然后是组合.除非您是模板元编程或想要多态行为,否则不要使用继承.有关详细信息,请参阅 Scott Meyers 的 Effective C++ 条目23:优先使用非成员非友元函数而不是成员函数.

Long story short -- don't use inheritance to tack on methods in C++. That's not idiomatic and results in problems with the language. Use non-friend, non-member functions where possible, followed by composition. Don't use inheritance unless you're template metaprogramming or want polymorphic behavior. For more information, see Scott Meyers' Effective C++ Item 23: Prefer non-member non-friend functions to member functions.

这是一个更完整的示例,显示了切片问题.您可以在 codepad.org

Here's a more complete example showing the slicing problem. You can see it's output on codepad.org

#include <ostream>
#include <iomanip>

struct Base
{
    int aMemberForASize;
    Base() { std::cout << "Constructing a base." << std::endl; }
    Base(const Base&) { std::cout << "Copying a base." << std::endl; }
    ~Base() { std::cout << "Destroying a base." << std::endl; }
};

struct Derived : public Base
{
    int aMemberThatMakesMeBiggerThanBase;
    Derived() { std::cout << "Constructing a derived." << std::endl; }
    Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; }
    ~Derived() { std::cout << "Destroying a derived." << std::endl; }
};

int SomeThirdPartyMethod(Base /* SomeBase */)
{
    return 42;
}

int main()
{
    Derived derivedObject;
    {
        //Scope to show the copy behavior of copying a derived.
        Derived aCopy(derivedObject);
    }
    SomeThirdPartyMethod(derivedObject);
}

这篇关于为什么不应该从 c++ std 字符串类派生?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆