【发布时间】:2018-12-05 13:22:34
【问题描述】:
我喜欢使用方法链来完全初始化对象,然后将它们存储在 const 变量中。在分析生成的代码时,事实证明这意味着执行许多复制构造函数。因此,我想知道 C++ 11 移动语义是否有助于优化方法链。
确实,通过将带有 ref 限定符的重载添加到我的链方法中,我已经能够显着加快我的代码速度。请考虑以下源代码:
#include <chrono>
#include <iostream>
#include <string>
#undef DEBUGGING_OUTPUT
#undef ENABLE_MOVING
class Entity
{
public:
Entity() :
data(0.0), text("Standard Text")
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Constructing entity." << std::endl;
#endif
}
Entity(const Entity& entity) :
data(entity.data), text(entity.text)
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Copying entity." << std::endl;
#endif
}
Entity(Entity&& entity) :
data(entity.data), text(std::move(entity.text))
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Moving entity." << std::endl;
#endif
}
~Entity()
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Cleaning up entity." << std::endl;
#endif
}
double getData() const
{
return data;
}
const std::string& getText() const
{
return text;
}
void modify1()
{
data += 1.0;
text += " 1";
}
Entity getModified1() const &
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Lvalue version of getModified1" << std::endl;
#endif
Entity newEntity = *this;
newEntity.modify1();
return newEntity;
}
#ifdef ENABLE_MOVING
Entity getModified1() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified1" << std::endl;
#endif
modify1();
return std::move(*this);
}
#endif
void modify2()
{
data += 2.0;
text += " 2";
}
Entity getModified2() const &
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Lvalue version of getModified2" << std::endl;
#endif
Entity newEntity = *this;
newEntity.modify2();
return newEntity;
}
#ifdef ENABLE_MOVING
Entity getModified2() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified2" << std::endl;
#endif
modify2();
return std::move(*this);
}
#endif
private:
double data;
std::string text;
};
int main()
{
const int interationCount = 1000;
{
// Create a temporary entity, modify it and store it in a const variable
// by taking use of method chaining.
//
// This approach is elegant to write and read, but it is slower than the
// other approach.
const std::chrono::steady_clock::time_point startTimePoint =
std::chrono::steady_clock::now();
for (int i = 0; i < interationCount; ++i)
{
const Entity entity = Entity().getModified1().getModified1().getModified2().getModified2();
#ifdef DEBUGGING_OUTPUT
std::cout << "Entity has text " << entity.getText() << " and data "
<< entity.getData() << std::endl;
#endif
}
const std::chrono::steady_clock::time_point stopTimePoint =
std::chrono::steady_clock::now();
const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
std::chrono::duration<double>>(stopTimePoint - startTimePoint);
std::cout << "Method chaining has taken " << timeSpan.count() << " seconds."
<< std::endl;
}
{
// Create an entity and modify it without method chaining. It cannot be
// stored in a const variable.
//
// This approach is optimal from a performance point of view, but it is longish
// and renders usage of a const variable impossible even if the entity
// won't change after initialization.
const std::chrono::steady_clock::time_point startTimePoint =
std::chrono::steady_clock::now();
for (int i = 0; i < interationCount; ++i)
{
Entity entity;
entity.modify1();
entity.modify1();
entity.modify2();
entity.modify2();
#ifdef DEBUGGING_OUTPUT
std::cout << "Entity has text " << entity.getText() << " and data "
<< entity.getData() << std::endl;
#endif
}
const std::chrono::steady_clock::time_point stopTimePoint =
std::chrono::steady_clock::now();
const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
std::chrono::duration<double>>(stopTimePoint - startTimePoint);
std::cout << "Modification without method chaining has taken "
<< timeSpan.count() << " seconds." << std::endl;
}
return 0;
}
没有方法链接的版本在这里比另一个快大约10倍。只要我更换
#undef ENABLE_MOVING
通过
#define ENABLE_MOVING
没有方法链接的版本仅比其他版本快 1.5 倍。所以这是一个很大的改进。
我仍然想知道是否可以进一步优化代码。当我切换到
#define DEBUGGING_OUTPUT
然后我可以看到每次调用 getModified1() 或 getModified2() 都会创建新实体。移动构造的唯一优点是创建更便宜。有没有办法甚至阻止移动构造并使用方法链接在原始实体上工作?
【问题讨论】:
-
但是移动在语义上不同于访问和访问 const ref 或 copy,在这种情况下它们是相同的,即保留原始对象。看来您的建议在功能上不一样。此外,根据
T&&的实现,您实际上可能最终会得到一个用于移动的复制 ctor。 -
嗯,你能详细说明一下吗,我不明白你的意思吗?移动构造函数really 移动成员字段
data,这是加速的原因。另外我猜想创建一个临时实例然后调用右值修改方法应该给出与创建变量并调用修改方法完全相同的结果。T&&是什么意思? -
我的意思是你建议用 move 代替 copy 听起来不太好。它不具有相同的不变量。
T&&我的意思是T(T&&)即移动 ctor。对于简单类型,它是一个副本,例如你不能真正移动int或double。也许我误解了您要避免哪些副本。他们有很多。即使在Entity getModified1() &&。任何通过引入强制移动,您都可以防止编译器允许复制省略,以支持基本上再次复制的移动,实际上会降低性能。也许我误解了你的问题。对不起,如果是这样。 -
如果您不介意修改原始实体,则只需让
modify1等人返回Entity&,如return *this;。然后你可以做const Entity entity = Entity().modify1().modify1().modify2().modify2();并且只调用一个移动构造函数。 -
@luk32:我已将 cmets 添加到时间测量代码中以阐明我的意图/问题。你说得对,
return std::move(...)可能会使复制省略成为不可能。但是省略std::move在这里没有帮助。如果启用调试输出(只需使用#define DEBUGGING_OUTPUT)并在 ref 限定符重载(Entity getModified*() &&))中将return std::move(*this);更改为return *this;,那么您会看到实体在每次调用getModified*() &&结束时被复制。这里的性能也下降了 10 倍。
标签: c++11 move-semantics method-chaining