C++ 仅仅因为复制可能很昂贵,就应该只移动一个类型吗?
我有一个类型是可复制的,但可能是昂贵的复制。我已经实现了move构造函数和move赋值。但我遇到了性能问题,人们在传递值时忘记调用move() 删除copy构造函数,并为实际需要副本的罕见情况提供显式copy()方法,这是一种好的C++11风格吗?这是其他语言的惯用语言(Ruby,JavaScript),但是我不知道C++标准库中禁止任何纯拷贝执行的任何东西。例如,std::vector是可复制的,而std::unique_ptr和std::thread由于其他原因是不可复制的 仅仅因为复制可能很昂贵,就应该只移动一个类型吗 否。如果类型的语义使得复制在概念上有意义,那么使复制可用的正确方法是实现一个复制构造函数,并让用户有机会采用标准语法来调用它:C++ 仅仅因为复制可能很昂贵,就应该只移动一个类型吗?,c++,c++11,move-semantics,C++,C++11,Move Semantics,我有一个类型是可复制的,但可能是昂贵的复制。我已经实现了move构造函数和move赋值。但我遇到了性能问题,人们在传递值时忘记调用move() 删除copy构造函数,并为实际需要副本的罕见情况提供显式copy()方法,这是一种好的C++11风格吗?这是其他语言的惯用语言(Ruby,JavaScript),但是我不知道C++标准库中禁止任何纯拷贝执行的任何东西。例如,std::vector是可复制的,而std::unique_ptr和std::thread由于其他原因是不可复制的 仅仅因为复制可能
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
否。如果该类型可复制,则该类型可复制。这意味着它的复制构造函数可用且有效。这并不意味着有某个成员函数的名称按顺序类似于字符
c
、o
、p
和y
,这“有点类似”。如果复制成本足够高,我会将该类视为不可复制的签名。从语义上讲,只有当你想复制的时候,东西才是可复制的,而昂贵的复制品是决定“不,不可复制”的一个正当理由
复制某些内容的能力并不意味着它需要在可复制的类型中实现。该类型的实现者可以决定它是否应该在语义上可复制
我不会将产生昂贵副本的操作称为“复制”,而是“克隆”或“复制”
您可以这样做:
#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);
}
#包括
模板
结构文档{
T const&T;
DoCopy(T const&T_):T(T_){
};
模板
文件副本(施工和测试){
返回t;
}
结构Foo{
结构费用目录{
整数[100000000];
};
费用副本*数据;
Foo():数据(新ExpensiveToCopy()){}
~Foo(){删除数据;}
Foo(Foo&&o):数据(o.data){o.data=nullptr;}
Foo&operator=(Foo&o){data=o.data;o.data=nullptr;return*this;}
Foo&运算符=(DoCopy o){
删除数据;
if(o.t.数据){
数据=新的费用副本(*o.t.data);
}否则{
数据=新的ExpensiveToCopy();
}
归还*这个;
}
Foo(DoCopy cp):数据(cp.t.data?new ExpensiveToCopy(*cp.t.data):new ExpensiveToCopy()){};
};
int main(){
富一;
//Foo two=1;//非法
Foo three=std::move(一);//合法
福四;
Foo five=复制(三);
四=标准::移动(三);
五份=复印件(四份);
}
这在某种程度上类似于在存在右值引用之前编写类似于std::move
的语义的方法,也有类似于这种技术的缺点,即语言本身不知道你在搞什么鬼把戏
它的优点是,上述do_copy
的语法类似于std::move
的语法,并且它允许您使用传统表达式,而无需创建Foo
的小实例,然后构造另一个变量的副本等
如果我们希望将其视为可复制的情况很常见(如果要避免的话),我会围绕知道
duplicate
方法的类编写一个复制包装器。我认为copy
成员函数的问题在于它会破坏使用赋值运算符的所有模板代码。在某些上下文中,不应通过复制构造复制可复制类型:virtual T*Clone()const=0
接口类(或非最终实现)表示可复制性,但不能作为复制构造函数实现。现在,value\u ptr
wrappers可以让你回到复制构造的世界,但是T
类本身是可复制的,但没有复制构造函数(至少不是公共的)。@Yakk:我不同意。任何标准容器的复制都可能非常昂贵,但它是可复制的。就我个人而言,我相信“复制”的操作有语言定义的语义和语言定义的语法。这两件事应该是一致的。我想你是想评论我的回答,而不是我的评论。:)在任何情况下,在标准容器中都有优先权:虽然转发列表有一个大小,并且可以实现size()
,但代价很高。因此,标准省略了size()
,如果你想知道大小,你必须自己计算。@TimCulver:当然不可复制的类型是有原因存在的,如果某些东西不需要复制,那么它就不应该执行它(我也喜欢移动语义使容器更灵活)。我的观点是,如果某些东西需要复制的语义,那么应该通过复制的语法来提供。因此,如果他们禁用复制构造,那么类型是不可复制的。它是可复制的、可克隆的,或者其他一些同义词,但显然不可复制。这是个什么问题?@Yakk:我同意你的分类。这是个问题吗?谁知道呢?取决于你想要什么类型的。但是你肯定不能再称它为可复制的,就许多其他代码而言(例如容器),你的类型永远不会被复制,因为你已经颁布了这样的法令。对,我不反对这种分析。我提议让m