C++ 这是复制两次值吗?

C++ 这是复制两次值吗?,c++,windows,qt,c++11,copy,C++,Windows,Qt,C++11,Copy,假设我有一个这样的类: class Foo { public: QString a; void setA(QString val); } void Foo::setA(QString val) { this->a = val; } 并实现如下setA(): class Foo { public: QString a; void setA(QString val); } void Foo::setA(QString val) {

假设我有一个这样的类:

class Foo
{
  public:
     QString a;
     void setA(QString val);
}
void Foo::setA(QString val)
{
  this->a = val;
}
并实现如下
setA()

class Foo
{
  public:
     QString a;
     void setA(QString val);
}
void Foo::setA(QString val)
{
  this->a = val;
}
然后像这样使用它:

Foo f;
QString v = "foo";
f.setA(v);
我是否在堆栈上复制了两次
v
结构?一个用于参数中的pass,另一个用于函数中的赋值,对吗?而是使用
void setA(QString&val)
将避免复制对象两次,因为在第一次复制中,我只是复制引用(指针),而不是整个对象,因此对象的唯一副本在赋值中:

  this->a = val;
因为您使用的是a,所以不会有2份副本。复制构造函数具有:

此操作需要固定的时间,因为QString是隐式共享的。这使得从函数返回QString非常快。如果一个共享实例被修改,它将被复制(写时复制),这需要线性时间

因此,在这种情况下,当您将
val
分配给
this->a

时,您只有一个副本,因为您使用的是a,所以不会有两个副本。复制构造函数具有:

此操作需要固定的时间,因为QString是隐式共享的。这使得从函数返回QString非常快。如果一个共享实例被修改,它将被复制(写时复制),这需要线性时间

因此,在这种情况下,当您将
val
分配给
this->a

我是否在堆栈上复制了两次v结构?一个用于参数中的pass,另一个用于函数中的赋值,对吗

对。您通过值传递,因此必须构造一个新对象(在您的情况下,通过复制构造函数)

现在,在
QString
(以及Qt中的许多其他值类型)的特定情况下,该操作与QString一样非常便宜(读取:引用计数,写入时复制)。对于其他数据类型,操作可能会很昂贵

而是使用void setA(QString&val)

或者实际上使用
setA(const QString&val)
,因为您没有修改
val
引用的对象。通过使用常量引用,还可以使用临时变量:

void setA(QString &val);
setA(QString("foo")); // does not compile!

void setA(const QString &val);
setA(QString("foo")); // works
因此,对象的唯一副本在赋值中

既然你的类存储了一个拷贝,那么一个常见的模式(参见现代有效C++进行大讨论)是坚持按值传递,并使用参数的移动赋值:

void setA(QString val) {
    this->a = std::move(val);
}
在这种情况下,如果使用临时/rvalue调用
setA
val
将构造move,然后再次移动,导致2次移动,而不是常量引用方法的1次复制+1次移动)。如果使用左值调用
setA
,您需要支付1个副本+1个移动(而不是1个const-reference方法副本)

我是否在堆栈上复制了两次v结构?一个用于参数中的pass,另一个用于函数中的赋值,对吗

对。您通过值传递,因此必须构造一个新对象(在您的情况下,通过复制构造函数)

现在,在
QString
(以及Qt中的许多其他值类型)的特定情况下,该操作与QString一样非常便宜(读取:引用计数,写入时复制)。对于其他数据类型,操作可能会很昂贵

而是使用void setA(QString&val)

或者实际上使用
setA(const QString&val)
,因为您没有修改
val
引用的对象。通过使用常量引用,还可以使用临时变量:

void setA(QString &val);
setA(QString("foo")); // does not compile!

void setA(const QString &val);
setA(QString("foo")); // works
因此,对象的唯一副本在赋值中

既然你的类存储了一个拷贝,那么一个常见的模式(参见现代有效C++进行大讨论)是坚持按值传递,并使用参数的移动赋值:

void setA(QString val) {
    this->a = std::move(val);
}

在这种情况下,如果使用临时/rvalue调用
setA
val
将构造move,然后再次移动,导致2次移动,而不是常量引用方法的1次复制+1次移动)。如果使用左值调用
setA
,您需要支付1个copy+1个move(而不是常量引用方法的1个副本)。

在第一种情况下,您复制构造QString以将其作为参数传递,然后将其复制分配给类成员,在第二种情况下,您只复制分配一次。所以总的来说你是对的。。但是QString在它的实现中使用引用计数,所以复制它不会像可能的那么重,并且按值传递在效率方面是可以的。我很确定这是允许优化器“删除”多余副本的情况之一。为什么依赖于此,而不是编写更清晰的代码:
void Foo::setA(QString const&val)
?在第一种情况下,您复制构造QString将其作为参数传递,然后将其复制分配给类成员,在第二种情况下,您只复制分配一次。所以总的来说你是对的。。但是QString在它的实现中使用引用计数,所以复制它不会像可能的那么重,并且按值传递在效率方面是可以的。我很确定这是允许优化器“删除”多余副本的情况之一。为什么依赖于此,而不是代码更清晰:
void Foo::setA(QString const&val)
?将
val
赋值给
a
也只会创建对同一字符串的另一个引用。将
val
赋值给
a
也只会创建对同一字符串的另一个引用。因此,字符根本不会被复制。