f(const string&)和f(const string)之间有什么区别?

时间:2009-12-25 11:35:27

标签: c++ const pass-by-reference pass-by-value

class mystring {
 friend ostream& operator<<(ostream &out, const mystring ss) {
        out << ss.s;
        return out;
    }
private:
    string s;
public:
    mystring(const char ss[]) {
        cout << "constructing mystring : " << ss << endl;
        s = ss;
    }
};

void outputStringByRef(const mystring &ss) {
 cout << "outputString(const string& ) " << ss << endl;
}

void outputStringByVal(const mystring ss) {
 cout << "outputString(const string ) " << ss << endl;
}

int main(void) {
    outputStringByRef("string by reference");
    outputStringByVal("string by value");
    outputStringByRef(mystring("string by reference explict call mystring consructor"));
    outputStringByVal(mystring("string by value explict call mystring constructor"));
} ///:~

考虑到上面的例子,我们不能修改pass-by-reference变量,也不能修改pass-by-value变量。每种方法的输出都是相同的。因为这两种方法没有区别,为什么C ++支持这两种方法?

感谢。

7 个答案:

答案 0 :(得分:20)

两者之间存在差异。请考虑以下事项:

#include <iostream>
#include <string>
using std::string;

string g_value;

void callback() {
    g_value = "blue";
}

void ProcessStringByRef(const string &s) {
    callback();
    std::cout << s << "\n";
}

void ProcessStringByValue(const string s) {
    callback();
    std::cout << s << "\n";
}

int main() {
    g_value = "red";
    ProcessStringByValue(g_value);
    g_value = "red";
    ProcessStringByRef(g_value);
}

输出:

red
blue

仅仅因为引用是函数内的const,并不意味着不能通过其他引用修改引用(具有多个引用或指向它的指针的情况称为“别名”)。因此,传递const引用和传递const值之间存在不同 - 在引用的情况下,对象可能在调用之后发生更改。在值的情况下,被调用者具有私有副本,该副本不会更改。

由于他们做了不同的事情,C ++允许你选择你想要的东西。

无论哪种方式都会对绩效产生影响 - 当您通过价值时,必须制作副本,这需要付出相应的代价。但是编译器然后知道只有你的函数可能有对该副本的任何引用,这可能允许其他优化。在返回callback()之前,ProcessStringByRef无法加载字符串的内容以进行打印。如果编译器认为这样做更快,ProcessStringByValue可以。

通常你关心副本,而不是指令的执行顺序,因为副本通常更昂贵。通常情况下,尽可能通过引用传递非常重要的对象进行复制。但是,即使实际上没有出现混叠,通过阻止某些优化,混叠的可能性有时会对性能产生严重影响。这就是为什么存在“严格别名规则”以及C99中的restrict关键字。

答案 1 :(得分:8)

f(const string&)接受const引用的字符串:f直接在引用传递的字符串对象上运行:不涉及复制。 const会阻止对原始对象的修改。

f(const string)采用字符串值,这意味着f会获得原始字符串的副本。即使您删除const,当按值传递时,f返回时对字符串的任何修改都会丢失。

我不清楚你的意思是“为什么C ++支持这两种方法?”。这只是适用的一般重载规则。

答案 2 :(得分:6)

f(string s)传递字符串s的值,换句话说,它创建一个副本并使用您传递的字符串的值对其进行初始化。对副本的任何更改都不会传播到您传递给调用函数的原始字符串。 在f(const string s)中,const是多余的,因为无论如何都无法改变原始值。

f(const string& s)中,不会复制字符串,但会传递对它的引用。这通常在你有一个大对象时完成,所以“pass-by-value”会产生开销(这就是c ++支持这两种方法的原因)。通过引用传递意味着您可以更改传递的“大”对象的值,但由于const说明符,您无法修改它。这是一种“保护”。

答案 3 :(得分:3)

您的对象可以包含mutable成员,即使使用const引用

也可以修改该成员

答案 4 :(得分:1)

使用outputStringByRef时,必须确保传递引用的变量保持活动状态,并且只要outputStringByRef需要它就保持不变。使用outputStringByVal您传递的变量可能会死亡或超出范围,而该函数的副本仍然可以。

答案 5 :(得分:1)

在能够修改函数中的字符串方面(几乎)没有区别。然而,在传递的内容方面存在很大差异。 const mystring &ss重载对字符串进行const引用。虽然它无法修改它,但它是相同的内存。如果字符串很长,这可能是一个很重要的因素(假设字符串未使用copy-on-write实现)。 const mystring ss表单正在复制字符串,因此将处理不同的内存。

实际上const mystring &ss表单可以更改字符串,如果使用了const_cast<mystring&> - 虽然我不建议这样做。

答案 6 :(得分:1)

想象一下,您想打印一个无法复制的对象:

Thread th;
// ...
cout << th; // print out informations...

参考版本不会复制线程,但会使用th的地址并为其创建别名。另一个版本会在传递值时尝试复制线程 - 但复制对于这样的对象可能没有意义(那么会有一个额外的线程吗?)。

可能会帮助您将复制对象视为克隆对象。在C ++中复制上面的th并不仅仅意味着拥有与Java相同的对象的另一个句柄 - 它意味着隐式克隆所表示的对象,并拥有它的完整副本。所以问题类似于“为什么Java同时支持Object.clone和复制引用?” - 两者有不同的目的。

毕竟,还有一个表现问题。您不希望复制每次传递资源饥饿对象的内容。

相关问题