草庐IT

c++ - 为什么不应该从 C++ 标准字符串类派生?

coder 2023-05-01 原文

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

上面写着:

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.

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

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

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

最佳答案

我认为这句话反射(reflect)了这里的困惑(强调我的):

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

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

  • 私有(private)继承,用于使用模板进行混合和面向方面的编程。
  • public 继承,仅用于多态情况编辑:好的,我想这也可以在一些混合场景中使用——例如 boost::iterator_facade——当 CRTP 出现时正在使用中。

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

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

但是,在 C++ 中,类是值类型——与大多数其他 OO 语言不同。最简单的查看方法是所谓的 the slicing problem。 .基本上,考虑:

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++ 中并非如此。

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

编辑:这是一个显示切片问题的更完整示例。您可以在 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++ - 为什么不应该从 C++ 标准字符串类派生?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6006860/

有关c++ - 为什么不应该从 C++ 标准字符串类派生?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  6. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  7. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  8. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  9. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  10. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

随机推荐