如何在不复制的情况下使用 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. 您可能不应该过多担心字符串复制。它有什么问题?而且我不明白为什么你不只是通过引用Bar
和 Baz
来传递字符串。
@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?的主要内容,如果未能解决你的问题,请参考以下文章