这是一个我从未真正理解过的内存分配问题.
void unleashMonkeyFish() { MonkeyFish * monkey_fish = new MonkeyFish(); std::string localname = "Wanda"; monkey_fish->setName(localname); monkey_fish->go(); }
在上面的代码中,我在堆上创建了一个MonkeyFish对象,为它指定了一个名称,然后在世界上释放它.假设分配的内存的所有权已经转移到MonkeyFish对象本身 - 只有MonkeyFish本身将决定何时死亡并自行删除.
现在,当我在MonkeyFish类中定义"name"数据成员时,我可以选择以下之一:
std::string name; std::string & name;
当我在MonkeyFish类中定义setName()函数的原型时,我可以选择以下之一:
void setName( const std::string & parameter_name ); void setName( const std::string parameter_name );
我希望能够最小化字符串副本.事实上,如果可以的话,我想完全消除它们.所以,似乎我应该通过引用传递参数......对吗?
让我感到困惑的是,一旦unleashMonkeyFish()函数完成,我的localname变量似乎将超出范围.这是否意味着我强行要通过副本传递参数?或者我可以通过引用传递它并以某种方式"逃脱它"?
基本上,我想避免这些情况:
我不想设置MonkeyFish的名称,只是当unleashMonkeyFish()函数终止时,localname字符串的内存消失.(这似乎非常糟糕.)
如果我可以帮助它,我不想复制字符串.
我宁愿不要新的localname
我应该使用什么原型和数据成员组合?
澄清:有几个答案建议使用static关键字来确保在unleashMonkeyFish()结束时不会自动解除分配内存.由于此应用程序的最终目标是释放N MonkeyFish(所有这些都必须具有唯一名称),因此这不是一个可行的选择.(是的,MonkeyFish - 变幻无常的生物 - 通常会在一天内改变他们的名字,有时会改变几次.)
编辑:Greg Hewgil指出将name变量存储为引用是非法的,因为它没有在构造函数中设置.我在问题中留下了错误,因为我认为我的错误(和格雷格的纠正)可能对第一次看到这个问题的人有用.
一种方法是使用你的字符串
std::string name;
作为对象的数据成员.然后,在unleashMonkeyFish函数中创建一个类似于你所做的字符串,并像你展示的那样通过引用传递它
void setName( const std::string & parameter_name ) { name = parameter_name; }
它会做你想要的 - 创建一个副本来将字符串复制到你的数据成员.如果您指定另一个字符串,则不必在内部重新分配新缓冲区.可能,分配新字符串只会复制几个字节.std :: string具有保留字节的能力.所以你可以叫"name.reserve(25);" 在你的构造函数中,如果你指定更小的东西,它可能不会重新分配.(我已经完成了测试,如果你从另一个std :: string分配,看起来像GCC总是重新分配,但是如果你从一个c-string分配它就不会.他们说他们有一个写时复制字符串,这可以解释为行为).
您在unleashMonkeyFish函数中创建的字符串将自动释放其分配的资源.这是这些对象的关键特征 - 他们管理自己的东西.类有一个析构函数,它们用于在对象死亡时释放已分配的资源,std :: string也是如此.在我看来,你不应该担心在函数中有std :: string local.无论如何,它不会对你的表现做任何明显的事情.一些std :: string实现(msvc ++ afaik)具有小缓冲区优化:对于一些小的限制,它们将字符保存在嵌入式缓冲区中,而不是从堆中分配.
编辑:
事实证明,对于具有高效swap
实现(恒定时间)的类,有更好的方法:
void setName(std::string parameter_name) { name.swap(parameter_name); }
这更好的原因是,现在调用者知道正在复制参数.现在,编译器可以轻松应用返回值优化和类似的优化.例如,考虑这种情况
obj.setName("Mr. " + things.getName());
如果你有setName
一个引用,那么在参数中创建的临时将绑定到该引用,并且在setName
其中将被复制,并且在它返回之后,临时将被销毁 - 无论如何这都是丢弃的产品.这只是次优,因为可以使用临时本身而不是其副本.使参数不是引用将使调用者看到正在复制参数,并使优化器的工作更容易 - 因为它不必内联调用以查看参数是否仍然被复制.
有关进一步说明,请阅读优秀文章 BoostCon09/Rvalue-References
如果使用以下方法声明:
void setName( const std::string & parameter_name );
那么你也会使用成员声明:
std::string name;
和setName
身体中的任务:
name = parameter_name;
您不能将该name
成员声明为引用,因为您必须在对象构造函数中初始化引用成员(这意味着您无法将其设置为setName
).
最后,您的std::string
实现可能仍然使用引用计数字符串,因此在赋值中不会生成实际字符串数据的副本.如果您担心性能,最好熟悉您正在使用的STL实现.