C++11 最佳实践:何时接受价值与恒量?
C++11 Best Practice: When to accept by value vs const&?
我对"规则"很满意,当你打算存储对象时,按值接受,当你只需要访问它时,按常量引用接受。这样,你(类的编写者)就不会选择是复制类变量的用户,还是移动它,他们会选择。但在使用过程中,我越来越不确定这个建议的合理性移动在成本操作上不是统一的。。。
struct Thing
{
std::array<int, 10000> m_BigArray;
};
class Doer
{
public:
Doer(Thing thing) : m_Thing(std::move(thing)) {}
private:
Thing m_Thing;
};
int main()
{
Thing thing;
Doer doer1(std::move(thing)); // user can decide to move 'thing'
// or
Doer doer2(thing); // user can decide to copy 'thing'
}
在上面的例子中,移动和复制一样昂贵。因此,与其获取const引用并复制对象一次,不如将其移动(实际上是复制)两次。用户在你的论点中做一次,你在你的成员中再做一次。
即使移动比复制便宜得多,这种情况也会进一步加剧(假设移动的东西是下面未知的成本,但比复制便宜):
struct A
{
A(Thing thing) : m_Thing(std::move(thing)) {}
Thing m_Thing;
};
struct B : A
{
B(Thing thing) : A(std::move(thing)) {}
};
struct C : B
{
C(Thing thing) : B(std::move(thing)) {}
};
struct D : C
{
D(Thing thing) : C(std::move(thing)) {}
};
在这里,你要么得到1个副本和4个移动,要么得到5个移动。如果所有构造函数都接受一个const引用,那么它将只有一个副本。现在我得权衡一下移动的代价,1次复制还是5次移动。
处理这些情况的最佳建议是什么?
处理这些情况的最佳建议是什么?
我最好的建议是做你正在做的事:自己想想。不要相信你听到的一切。测量
下面我获取了您的代码,并用print语句对其进行了插入。我还添加了第三种情况:从prvalue初始化:
测试尝试两种方式:
- 传递值
- 经过
const&
和&&
时过载:
代码:
#include <utility>
#include <iostream>
struct Thing
{
Thing() = default;
Thing(const Thing&) {std::cout << "Thing(const Thing&)n";}
Thing& operator=(const Thing&) {std::cout << "operator=(const Thing&)n";
return *this;}
Thing(Thing&&) {std::cout << "Thing(Thing&&)n";}
Thing& operator=(Thing&&) {std::cout << "operator=(Thing&&)n";
return *this;}
};
class Doer
{
public:
#if PROCESS == 1
Doer(Thing thing) : m_Thing(std::move(thing)) {}
#elif PROCESS == 2
Doer(const Thing& thing) : m_Thing(thing) {}
Doer(Thing&& thing) : m_Thing(std::move(thing)) {}
#endif
private:
Thing m_Thing;
};
Thing
make_thing()
{
return Thing();
}
int main()
{
Thing thing;
std::cout << "lvaluen";
Doer doer1(thing); // user can decide to copy 'thing'
std::cout << "nxvaluen";
Doer doer2(std::move(thing)); // user can decide to move 'thing'
std::cout << "nprvaluen";
Doer doer3(make_thing()); // user can decide to use factor function
}
对我来说,当我用-DPROCESS=1编译时,我得到:
lvalue
Thing(const Thing&)
Thing(Thing&&)
xvalue
Thing(Thing&&)
Thing(Thing&&)
prvalue
Thing(Thing&&)
并且DPROCESS=2:
lvalue
Thing(const Thing&)
xvalue
Thing(Thing&&)
prvalue
Thing(Thing&&)
因此,在传递lvalue和xvalue的重载引用时,传递值需要额外的move构造。正如你所指出的,移动建筑并不一定便宜。它可能和复制结构一样昂贵。从好的方面来说,您只需要编写1个带有传递值的重载。传递重载引用需要2^N个重载,其中N是参数数。在N==1时非常可行,在N==3时变得笨拙。
正如你所指出的,你的第二个例子只是你的第一个例子。
当性能是您主要关心的问题时,尤其是当您不能指望廉价的移动构造函数时,请传递重载的右值引用。当你可以指望廉价的移动结构,和/或你不想处理不合理的(你可以自己定义不合理的)过载数量时,使用传递值。没有一个答案对任何情况下的每个人都是正确的。C++11程序员仍然需要思考。
您没有将其作为一种选择,但我可以建议完美转发吗?
class Doer
{
public:
template<typename T>
Doer(T&& thing) : m_Thing(std::forward<T>(thing)) {}
private:
Thing m_Thing;
};
这将导致lvalues/rvalues的单个复制/移动,这正是我们想要的。
经常被引用的想要速度?传递值。对这个问题进行了详细的处理:
尽管当函数参数通过值传递时,编译器通常需要进行复制(因此对函数内部参数的修改不会影响调用方),但当源是右值时,编译器可以省略复制,只使用源对象本身。
仅部分回答,但在您给出的第二个示例中,可以通过显式继承A的基ctor:来避免移动
struct B : A
{
using A::A;
};
struct C : B
{
using A::A;
};
struct D : C
{
using A::A;
};
在这里,你将以1次复制对1次移动结束。
以下是一些可以解决问题的附加规则:
- 永远不要从具体的类派生。一个具体的、可实例化的类已经完全实现。从中派生是对继承的不良利用。=>构造函数参数成本消失
- 根据数据是在当前对象内部还是外部,决定数据成员中的按值传递和按常量传递引用
- 如果一个类中有构造函数参数,那么在同一个类中也应该有一个数据成员。将参数传递给基类不是一个好主意
- 忘记std::move。复制数据的成本不够大。程序员在任何地方键入std::move所花费的时间都比实践节省的执行时间要多
- 如果你不断地创建和销毁这些对象,那么这比你用std::move保存的东西要花费更多的时间
- 何时需要修改套接字的接收缓冲区大小?
- MSVC 编译器/链接器何时合成标量/矢量删除析构函数
- 使用信号检测子进程何时终止的最佳方法是什么?
- C++ 关闭Boost ASIO SSL套接字的最佳方法是什么?
- 套接字发送(.)线程的最佳数量
- 跨平台套接字发送,Linux 上的缓冲区常量无效* Windows上的常量字符*,最佳处理方式
- C++,在阻塞模式下从套接字读取所有可用字节的最佳方法
- 如何确定网络堆栈何时准备好再次打开到同一主机/端口的套接字?
- 将超时值设置为套接字轮询/选择的最佳实践是什么?
- web服务器如何知道何时完全接收到HTTP请求
- 要打开以供C++中的应用程序最佳使用的并行套接字/TCP连接数
- 如何添加到链接列表中以获得最佳拟合管理或删除某些内容
- 何时链接并何时包含
- RAII 套接字:何时释放(关闭)
- 如何在使用WSARecv()和IOCP时知道套接字何时接收到FIN数据包
- 组织C++套接字服务器的最佳方式是什么
- 何时使用智能指针删除带有asio和async_accepet的套接字
- 如何检测提升 TCP 套接字何时断开连接
- 链接器何时可以在多重定义的符号上出错
- 何时使用外部链接初始化全局常量,避免静态初始化顺序惨败