c++11 - 在 C++ 中进行方法链接时如何避免复制
问题描述
我喜欢使用方法链来完全初始化对象,然后将它们存储在 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() 都会创建新实体。移动构造的唯一优点是创建更便宜。有没有办法甚至阻止移动构造并使用方法链接在原始实体上工作?
解决方案
在 Igor Tandetnik 的帮助下,我想我可以回答我的问题了!
必须更改修改方法以返回右值引用:
#ifdef ENABLE_MOVING
Entity&& getModified1() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified1" << std::endl;
#endif
modify1();
return std::move(*this);
}
#endif
#ifdef ENABLE_MOVING
Entity&& getModified2() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified2" << std::endl;
#endif
modify2();
return std::move(*this);
}
#endif
并且初始化必须像这样发生:
const Entity entity = std::move(Entity().getModified1().getModified1().getModified2().getModified2());
然后方法链接代码几乎与其他代码一样有效。不同之处在于对移动构造函数的一次调用和对临时实例的一次额外的析构函数调用,这可以忽略不计。
感谢您的帮助!
推荐阅读
- python - 创建二叉树
- javascript - 除了作用域之外,还有什么其他原因为数组本身提供了对数组本身的引用?
- python - 将字符串转换为布尔值仅给出 False 值
- google-sheets - 有没有办法收集访问 Google 站点的 Google ID?
- javascript - 获取网站的控制台日志并发送到 websocket
- reactjs - 在一个页面上使用引导程序,而不是在 reactjs 项目中的另一个页面上
- java - 在 Spring Boot 中找不到 Elasticsearch 方法
- c++ - 是否可以使用单个定义同时定义函数的 const 和常规版本?(使用模板、自动、decltype 等)
- c# - 为什么游戏对象蓝轴朝前,但枪在红轴上而不是蓝色轴上?
- java - 为什么Java中的JTextfield在运行时不显示文本?