10

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

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

4

3 回答 3

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
于 2013-04-06T19:40:29.813 回答
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);
}

这有点类似于在右值引用存在之前您可以编写std::move类似语义的方式,与此类技术具有相似的缺点,即语言本身不知道您在做什么恶作剧。

它的优点是上面do_copy的语法类似于 的语法std::move,并且它允许您使用传统表达式,而无需创建琐碎的实例Foothen 构造另一个变量的副本等。

如果我们希望将其视为可复制的情况很常见(如果要避免),我会在知道该duplicate方法的类周围编写一个复制包装器。

于 2013-04-06T19:49:10.463 回答
1

不。如果类型是可复制的,那么该类型是可复制的。这意味着它的复制构造函数可用并且可以工作。这并不意味着某些成员函数的名称看起来像字符c, o,p并且y按顺序执行“几乎是类似的事情”。

于 2013-04-06T19:41:45.967 回答