一个类型是否只能移动,只是因为复制可能很昂贵?

时间:2013-04-06 19:37:20

标签: c++ c++11 move-semantics

我有一个可复制的类型,但复制起来可能很昂贵。我已经实现了移动构造函数和移动赋值。但是我遇到性能问题,人们忘记在传递值时调用move()。

删除复制构造函数是不是很好的C ++ 11样式,而是在实际需要复制的罕见情况下提供显式的copy()方法?这在其他语言(Ruby,JavaScript)中是惯用的,但我不知道C ++标准库中的任何内容都禁止纯粹为了性能而复制。例如,std :: vector<>是可复制的,而std :: unique_ptr<>和std :: thread由于其他原因是不可复制的。

3 个答案:

答案 0 :(得分:11)

  

一个类型是否只能移动,只是因为复制可能很昂贵?

即可。如果你的类型的语义是复制它在概念上有意义,那么正确的复制方法是实现一个复制构造函数,并让用户有机会采用标准语法来调用它:

T a;
T a = b;

如果人们忘记移动他们不想再使用的物体了......嗯,这是他们的坏事:

T c = std::move(a); // I'm doing it right (if I no longer need object a);
T d = b; // If I don't need b anymore, I'm doing it wrong.

如果(出于任何原因)你的某些函数,总是希望调用者提供一个可以移动的对象,然后让函数接受一个右值引用:

void foo(my_class&& obj);

my_class a;
foo(a); // ERROR!
foo(std::move(a)); // OK

答案 1 :(得分:4)

如果副本足够昂贵,我会将该类视为签名中不可复制的。语义上的东西只有在你想要的时候才是可复制的,而昂贵的副本是决定“不,不可复制”的正当理由。

复制某些内容的能力并不意味着它需要在可复制的类型中实现。该类型的实现者可以决定它是否应该在语义上可复制。

我不会将产生昂贵副本的副本称为“副本”,而是“克隆”或“重复”。

您可以这样做:

#include <utility>

template<typename T>
struct DoCopy {
  T const& t;
  DoCopy( T const& t_ ):t(t_) {}
};
template<typename T>
DoCopy<T> do_copy( T const& t ) {
  return t;
}
struct Foo {
  struct ExpensiveToCopy {
    int _[100000000];
  };
  ExpensiveToCopy* data;
  Foo():data(new ExpensiveToCopy()) {}
  ~Foo(){ delete data; }
  Foo(Foo&& o):data(o.data) { o.data = nullptr; }
  Foo& operator=(Foo&& o) { data=o.data; o.data=nullptr; return *this; }
  Foo& operator=(DoCopy<Foo> o) {
    delete data;
    if (o.t.data) {
      data=new ExpensiveToCopy(*o.t.data);
    } else {
      data=new ExpensiveToCopy();
    }
    return *this;
  }
  Foo( DoCopy<Foo> cp ):data(cp.t.data?new ExpensiveToCopy( *cp.t.data ):new ExpensiveToCopy() ) {};
};
int main() {
  Foo one;
  // Foo two = one; // illegal
  Foo three = std::move(one); // legal
  Foo four;
  Foo five = do_copy(three);
  four = std::move(three);
  five = do_copy(four);
}

这有点类似于在rvalue引用存在之前编写std::move语义的方式,这些技术有类似的缺点,即语言本身不知道你要做什么恶作剧

它的优点是上述do_copy的语法类似于std::move的语法,它允许您使用传统表达式而无需创建Foo的简单实例然后构建另一个变量的副本等。

如果我们想要将其视为可复制的情况很常见(如果要避免),我会在知道duplicate方法的类周围编写一个copy-wrapper。

答案 2 :(得分:1)

没有。如果类型是可复制的,则类型是可复制的。这意味着它的复制构造函数可用,工作。这并不意味着有一些成员函数,其名称在序列中看起来像copy,这类似于“类似的东西” ”