如何在不复制的情况下使用 std::string?

Posted

技术标签:

【中文标题】如何在不复制的情况下使用 std::string?【英文标题】:How to use a std::string without copying? 【发布时间】:2014-10-17 07:10:08 【问题描述】:

我有一个班级说,

class Foo

   public:
      void ProcessString(std::string &buffer)
      
          // perform operations on std::string

          // call other functions within class
          // which use same std::string string
      

      void Bar(std::string &buffer)
      
          // perform other operations on "std::string" buffer
      

      void Baz(std::string &buffer)
      
          // perform other operations on "std::string" buffer
      
;

此类尝试使用std::string 缓冲区在这些条件下使用各种方法对其执行操作:

我不想传递我已经拥有的std::string 的副本。 我不想创建此类的多个对象。

例如:

// Once an object is created
Foo myObject;

// We could pass many different std::string's to same method without copying
std::string s1, s2, s3;
myObject.ProcessString(s1);
myObject.ProcessString(s2);
myObject.ProcessString(s3);

我可以使用该字符串并将其分配为类成员,以便其他使用的函数可以知道它。

但似乎我们不能有引用类成员std::string &buffer,因为它只能从构造函数初始化。

我可以使用指向std::string 的指针,即std::string *buffer 并将其用作类成员,然后传递s1, s2, s3 的地址。

class Foo

   public:
      void ProcessString(std::string *buf)
      
          // Save pointer
          buffer = buf;

          // perform operations on std::string

          // call other functions within class
          // which use same std::string string
      

      void Bar()
      
          // perform other operations on "std::string" buffer
      

      void Baz()
      
          // perform other operations on "std::string" buffer
      
   private:
       std::string *buffer;
;

或者,另一种方法是向每个函数传递对std::string 缓冲区的引用,就像在上面的第一个示例中所示

这两种方法看起来有点难看,因为我很少看到使用 std::string 作为指针或将类的所有函数传递给相同的参数。

有没有更好的解决方法或者我正在做的事情还不错?

【问题讨论】:

您已经在使用字符串而不进行复制。您通过引用传递,即 std::string &s. 您可能不应该过多担心字符串复制。它有什么问题?而且我不明白为什么你不只是通过引用 BarBaz 来传递字符串。 @ChristianHackl 问题主要出在性能上,复制一个对象在 CPU 和内存方面都是昂贵的,而且事实上你不能在调用者中修改一个对象 [不完全替换它],除非你通过它指针或引用。 先生,我在菜鸟时代已经复制了足够多的字符串,知道它确实会导致性能下降,严重程度取决于您使用字符串的密集程度。我知道我在说什么。 我希望现在更清楚了。 【参考方案1】:

在 MyObject 中保留不属于您的对象的字符串的引用或指针是危险的。很容易得到讨厌的未定义行为

看下面的法律例子(酒吧是公开的):

myObject.ProcessString(s1);     // start with s1 and keep its address
myObject.Bar();                 // works with s1 (using address previously stored) 

看下面的UB:

if (is_today) 
    myObject.ProcessString(string("Hello"));  // uses an automatic temporary string
                                             // !! end of block: temporary is destroyed!
else 
    string tmp = to_string(1234);            // create a block variable 
    myObject.ProcessString(tmp);             // call the main function 
                                            // !! end of block:  tmp is destroyed
myObject.Bar();  // expects to work with pointer, but in reality use an object that was already destroyed !!  => UB                              

错误非常严重,因为在读取函数的用法时,一切似乎都正常且管理良好。自动销毁 bloc 变量隐藏了问题。

所以如果你真的想避免复制字符串,你可以按照你的设想使用一个指针,但你只能在由 ProcessString() 直接调用的函数中使用这个指针,并将这些函数设为私有。

在所有其他情况下,我强烈建议重新考虑您的立场,并设想:

将使用它的对象中字符串的本地副本。 或者在所有需要它的对象函数中使用string&参数。这避免了副本,但留给调用者组织正确管理字符串的责任。

【讨论】:

【参考方案2】:

你基本上需要回答这个问题:谁拥有字符串? Foo 是否拥有该字符串?外部调用者是否拥有该字符串?还是他们都共享字符串的所有权。

“拥有”字符串意味着字符串的生命周期与其相关。因此,如果 Foo 拥有该字符串,则当 Foo 停止存在或销毁它时,该字符串将停止存在。共享所有权要复杂得多,但我们可以说只要任何所有者保留该字符串,该字符串就会存在。

每种情况都有不同的答案:

    Foo 拥有字符串:将字符串复制到Foo,然后让成员方法对其进行变异。 外部资源拥有该字符串:Foo 不应在其自己的堆栈之外持有对该字符串的引用,因为该字符串可能在其不知情的情况下被销毁。这意味着它需要通过引用传递给使用它但不拥有它的每个方法,即使这些方法在同一个类中。 共享所有权:创建字符串时使用shared_ptr,然后将该shared_ptr 传递给共享所有权的每个实例。然后,您将 shared_ptr 复制到一个成员变量,并且方法可以访问它。这比通过引用传递的开销要高得多,但如果您想要共享所有权,这是最安全的方法之一。

实际上还有其他几种建模所有权的方法,但它们往往更深奥。所有权弱、所有权可转让等。

【讨论】:

【参考方案3】:

既然你的要求是这样的

1.我不想传递我已经拥有的 std::string 的副本。

2.我不想创建这个类的多个对象。

使用通过 ref 将是 1 的解决方案 使用 static 将是 2 的解决方案。由于它是一种静态成员方法,因此该方法只有一个副本。但是,它不属于任何对象。话虽如此,您可以直接调用此方法,而不是通过对象。

例如,

class Foo

      static void ProcessString(std::string &s)
      
          // perform operations on std::string

          // call other functions within class
          // which use same std::string string
      


当你调用这个方法时,它会是这样的:

std::string s1, s2, s3;
Foo::ProcessString(s1);
Foo::ProcessString(s2);
Foo::ProcessString(s3);

更进一步,如果你只想要这个类的一个实例,你可以参考单例设计模式。

【讨论】:

“如果你只想要这个类的一个实例,你可以参考单例设计模式。” 如果你只想要一个类的实例,这里有一个更简单的模式: Foo f; 我什至不相信课程是必需的。为什么不只是一个namespace 可能是出于某种原因,这超出了这个问题的范围。总而言之,代码满足用户的要求。 @AndreKostur @ChristianHackl 不太确定你在说什么。不使用单例,这个类的多个实例是可能的。 @STNYU:当然,我的评论很讽刺。你会发现现在 Singleton 往往被认为是一种反模式。它实际上是一个混淆的全局变量。您会在 SO 和其他网站上找到关于该主题的大量材料。本质上,class 仅允许单个实例的纯粹想法本身就是矛盾的。 “类”一词的意思是:可以存在许多实例的东西。如果一个类的多个实例由于某种原因伤害了你,那么最好解决这个原因。如果您仍然不想要多个实例,请不要创建它们。

以上是关于如何在不复制的情况下使用 std::string?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从一个字符串本地保存数据,然后在不将其移动到其他地方的情况下进行破坏?

使用 std::string 打开文件

如何在不使用滤镜的情况下使图像变暗? [复制]

如何在不使用 each() 的情况下重写此函数? [复制]

如何在不使用“+”运算符的情况下添加两个变量? [复制]

如何在不使用 dest 变量的情况下使用 argparse? [复制]