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

时间:2018-12-05 13:22:34

标签: c++11 move-semantics method-chaining

我喜欢使用方法链接来完全初始化对象,然后将它们存储在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()的调用都创建了新实体。移动构建的唯一优点是创建成本较低。有没有一种方法可以阻止移动构造并通过方法链接在原始实体上工作?

1 个答案:

答案 0 :(得分:-1)

我想在Igor Tandetnik的帮助下,我可以回答我的问题!

必须更改修改方法以返回右值引用:

$additional_condition = 'AND active=1'
$statement = 'SELECT * FROM users WHERE condition=1 %s';
echo sprintf($statement, $additional_condition);

// Output: SELECT * FROM users WHERE condition=1 AND active=1

并且初始化必须像这样发生:

#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

然后,方法链接代码几乎与其他代码一样有效。区别在于,对移动构造函数的一次调用和对临时实例的另一种析构函数的调用可以忽略不计。

谢谢您的帮助!