在 C++ 中进行方法链接时如何避免复制

How to Avoid Copies When Doing Method Chaining in C++

我喜欢使用方法链来完全初始化对象,然后将它们存储在常量变量中。分析生成的代码后发现,这意味着执行了许多复制构造函数。因此我想知道 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 的帮助下,我想我可以回答我的问题!

修改方法必须改为return右值引用:

#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());

然后方法链接代码几乎和其他代码一样高效。不同之处在于对移动构造函数的一次调用和对临时实例的一次附加析构函数调用可以忽略不计。

感谢您的帮助!