繁体   English   中英

C ++ 11最佳实践:何时接受值与const&?

[英]C++11 Best Practice: When to accept by value vs const&?

我对“规则”感到满意,“规则”在您打算存储对象时按值接受,而在仅需要访问它时按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个副本。 现在,我必须权衡昂贵的举动(一本还是五本)。

处理这些情况的最佳建议是什么?

处理这些情况的最佳建议是什么?

我最好的建议是做自己在做的事情:自己考虑。 不要相信您听到的一切。 测量。

下面,我采用了您的代码,并使用打印语句对其进行了检测。 我还添加了第三种情况:从prvalue初始化:

该测试尝试两种方式进行操作:

  1. 传递价值。
  2. 通过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 << "lvalue\n";
    Doer doer1(thing); // user can decide to copy 'thing'
    std::cout << "\nxvalue\n";
    Doer doer2(std::move(thing)); // user can decide to move 'thing'
    std::cout << "\nprvalue\n";
    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&&)

因此,按值传递比传递左值和xvalue个案的重载引用要花费额外的移动构造。 正如您已经指出的,移动结构不一定便宜。 它可能与复制结构一样昂贵。 从好的方面来说,您只需要编写1个带有值传递的重载即可。 通过重载的引用传递需要2 ^ N个重载,其中N是参数的数量。 在N == 1时相当可行,在N == 3时变得笨拙。

同样,正如您指出的,您的第二个例子只是您的第一个例子。

当性能是您最关心的问题时,尤其是当您不能依靠便宜的move构造函数时,请传递重载的右值引用。 当您可以依靠便宜的搬家结构,和/或不想处理不合理的(您自己要定义不合理的)过载时,请使用值传递。 在每种情况下,没有一个答案永远适合所有人。 C ++ 11程序员仍然必须思考。

经常被引用想要速度吗? 通过价值传递。 给这个问题一个详细的处理方法:

...尽管通常在值传递函数参数时要求编译器进行复制(因此,对函数内部参数的修改不会影响调用者),但可以取消复制,而只需使用当源是右值时,源对象本身。

您没有将其作为选择,但是我可以建议完美的转发吗?

class Doer
{
public:
    template<typename T>
    Doer(T&& thing) : m_Thing(std::forward<T>(thing)) {}

private:
    Thing m_Thing;
};

这将导致左值/右值的单个复制/移动,这正是我们想要的。

仅提供部分答案,但在您提供的第二个示例中,可以通过显式继承A的基本ctor来避免移动:

struct B : A
{
    using A::A;
};

struct C : B
{
    using A::A;
};

struct D : C
{
    using A::A;
};

在这里,您将获得1个副本对1个动作。

以下是一些可以解决该问题的其他规则:

  1. 永远不要从具体的课程中衍生出来。 一个具体的,可实例化的类已经完全实现。 从它派生是对继承的错误使用。 =>您的构造函数参数成本消失了。
  2. 根据数据是在当前对象内部还是外部来确定数据成员中的值传递和常量引用传递。
  3. 如果在一个类的构造函数的参数,也应该有在同一类的数据成员。 将参数传递给基类不是一个好主意。
  4. 忘记std :: move。 复制数据的成本不够大。 程序员在所有地方键入std :: move所花的时间要比练习节省的执行时间多。
  5. 如果您不断创建和销毁这些对象,则与使用std :: move保存的时间相比,这将花费更多的时间。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM