2013-04-06 62 views
10

我有一个可复制的类型,但复制可能会很昂贵。我已经实现了移动构造函数和移动赋值。但是我有性能问题,人们忘记了在传值时调用move()。某种类型是否应该是仅移动的,仅仅是因为复制可能很昂贵?

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

+2

我想'复制'成员函数的问题是它打破了所有使用赋值运算符的模板代码。 – Pubby 2013-04-06 19:40:20

回答

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

在某些上下文中,不应该通过复制构造来复制可复制类型:接口类(或非最终实现)的'virtual T * Clone()const = 0'表示可复制性,但不能作为复制构造函数实现。现在''value_ptr '包装可以让你回到复制构造的世界,但是'T'类本身是可复制的,但是没有复制构造函数(至少不是公共函数)。 – Yakk 2013-04-06 19:52:28

+1

@Yakk:我不同意。任何标准容器的复制都可能非常昂贵,但它是可复制的。就我个人而言,我相信“复制”的操作具有语言定义的语义和语言定义的语法。这两件事应该匹配。 – 2013-04-06 19:55:19

+0

我想你的意思是评论我的答案,而不是我的评论。 :) – Yakk 2013-04-06 19:57:14

1

不。如果类型是可复制的,那么类型是可复制的。这意味着它的拷贝构造函数可用,并且工作。这并不意味着有一些成员函数的名字看起来像是字符串c,o,py,它的确“排序几乎类似的东西”。

+0

所以,如果他们禁用复制构造,那么类型是不可复制的。它是可复制的,可复制的,或者其他一些别的同义词,但是显然不可复制。这是一个什么问题? – Yakk 2013-04-06 19:54:55

+1

@Yakk:我同意你的分类。这是个问题吗?那么,谁知道?取决于你的类型。但是您当然不会再将它称为可复制的,并且就许多其他许多代码而言(例如容器),您的类型根本不会被复制,因为您已经规定了它的类型。 – 2013-04-06 20:01:34

+0

对,我不反对分析。尽管事实上它显然是一种价值类型,但我打算让我的类型不可复制。 – 2013-04-07 18:44:59

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语法,以及它允许​​使用传统的表达式,而无需创建的Foo琐碎实例然后构造的另一个变量等的副本

如果我们想把它当作可复制的情况是常见的(如果要避免),我会在知道duplicate方法的类上写一个复制包装。

+1

我会让我的图书馆的用户成为法官。 – 2013-04-06 20:02:51

+0

我喜欢这个解决方案。我为可复制的层次结构做了类似的事情,而且类似于移动的语法是一个很好的接触。 – 2013-04-09 14:36:46

相关问题