【问题标题】:Passing/Moving parameters of a constructor in C++0x在 C++0x 中传递/移动构造函数的参数
【发布时间】:2011-10-05 00:58:03
【问题描述】:

如果我有一个带有 n 个参数的构造函数,这样任何参数都可以是右值和左值。是否可以通过右值的移动语义来支持这一点,而无需为每个可能的右值/左值组合编写 2^n 构造函数?

【问题讨论】:

  • 给出代码示例,更清楚。 (谁知道呢,可能你根本不需要move 构造函数!)
  • 你能显示你当前的代码吗?据我所知,移动构造函数是“特殊”构造函数Foo(Foo&& that),所以我对你在说什么有点困惑。
  • @zneak:移动构造函数只会引用同一个类,我认为 Sid 正在谈论将任何“n”参数传递给构造函数。 @Sid:如果您尝试将每个参数传递给单独的函数(而不是构造函数),那么您将遇到 2*n 个函数的最坏情况,一个用于右值,一个用于每个参数的左值。
  • @zneak, @Aditya:是的,我在谈论将任何“n”参数传递给构造函数。我很着急,所以我把这个问题命名为非常糟糕。我会解决的。

标签: c++ c++11 rvalue-reference move-constructor


【解决方案1】:

你按值取每一个,像这样:

struct foo
{
    foo(std::string s, bar b, qux q) :
    mS(std::move(s)),
    mB(std::move(b)),
    mQ(std::move(q))
    {}

    std::string mS;
    bar mB;
    qux mQ;
};

参数对函数参数的初始化将是复制构造函数或移动构造函数。从那里,您只需将函数参数值移动到您的成员变量中。

记住:复制和移动语义是由班级提供的服务,而不是你自己提供的服务。在 C++0x 中,您不再需要担心如何获取自己的数据“副本”;只是要求它,让班级去做:

foo f("temporary string is never copied", bar(), quz()); // no copies, only moves
foo ff(f.mS, f.mB, f.mQ); // copies needed, will copy
foo fff("another temp", f.mB, f.mQ); // move string, copy others

注意:您的构造函数只接受值,这些值将弄清楚如何构造自己。当然,您可以从那里将它们移动到您想要的位置。

这适用于任何地方。有需要复制的功能吗?使其在参数列表中:

void mutates_copy(std::string s)
{
    s[0] = 'A'; // modify copy
}

mutates_copy("no copies, only moves!");

std::string myValue = "don't modify me";
mutates_copy(myValue); // makes copy as needed
mutates_copy(std::move(myValue)); // move it, i'm done with it

在 C++03 中,你可以很好地模拟它,但它并不常见(根据我的经验):

struct foo
{
    foo(std::string s, bar b, qux q)
    // have to pay for default construction
    {
        using std::swap; // swaps should be cheap in any sane program

        swap(s, mS); // this is effectively what
        swap(b, mB); // move-constructors do now,
        swap(q, mQ); // so a reasonable emulation
    }

    std::string mS;
    bar mB;
    qux mQ;
};

【讨论】:

  • +1 太好了,在新世界中,您只需按价值衡量论点,一切都会尽善尽美。那好极了。为了好玩,你可以添加赋值运算符吗?
  • @Kerrek:如果您需要参数的副本,则可以获取值,但如果您只需要检查它们(常量引用)或修改它们(引用),则不需要。我相信您可能知道这一点,但阅读您评论的人可能不知道。
  • mutates_copy("no copies, only moves!") 确实在 std::string ctor 中复制字符串文字,无法将字符串文字移动到 std::string
  • foo(std::string s, bar b, qux q) : mS(s)... 效果更好,因为当源是右值时,编译器可以省略复制 ctor,这比移动更好。
  • @Gene:但是,你不是在处理右值。您可能已将右值传递给 foo 构造函数。然后将它们移动到左值 s、b 和 q 中。在您的版本中,您直接使用 s 来构造 mS。 s not 是一个右值,因此既不会发生省略也不会发生移动构造。您可以使用嘈杂的构造函数来演示它。 ideone.com/CRbaA
【解决方案2】:

取下面的代码ideone link.

#include <iostream>

class A
{
public:
  A() : i(0) {}
  A(const A& a) : i(a.i) { std::cout << "Copy A" << std::endl; }
  A(A&& a) : i(a.i) { std::cout << "Move A" << std::endl; }
  int i;
};

template <class T>
class B1
{
public:
  template <class T1, class T2>
  B1(T1&& x1_, T2&& x2_) : x1(std::forward<T1>(x1_)), x2(std::forward<T2>(x2_)) {}
  B1(const B1<T>& x) : x1(x.x1), x2(x.x2) { std::cout << "Copy B1" << std::endl; }
  B1(B1<T>&& x) : x1(std::move(x.x1)), x2(std::move(x.x2)) { std::cout << "Move B1" << std::endl; }
private:
  T x1;
  T x2;
};

template <class T>
class B2
{
public:
  B2(T x1_, T x2_) : x1(std::move(x1_)), x2(std::move(x2_)) {}
  B2(const B2<T>& x) : x1(x.x1), x2(x.x2) { std::cout << "Copy B2" << std::endl; }
  B2(B2<T>&& x) : x1(std::move(x.x1)), x2(std::move(x.x2)) { std::cout << "Move B2" << std::endl; }
private:
  T x1;
  T x2;
};

A&& inc_a(A&& a) { ++a.i; return static_cast<A&&>(a); }
A inc_a(const A& a) { A a1 = a; ++a1.i; return a1; }

int main()
{
  A a1;
  A a2;
  std::cout << "1" << std::endl;
  B1<A> b1(a1,a2);
  std::cout << "2" << std::endl;
  B1<A> b2(a1,A());
  std::cout << "3" << std::endl;
  B1<A> b3(A(),a2);
  std::cout << "4" << std::endl;
  B1<A> b4(A(),A());
  std::cout << "5" << std::endl;
  B2<A> b5(a1,a2);
  std::cout << "6" << std::endl;
  B2<A> b6(a1,A());
  std::cout << "7" << std::endl;
  B2<A> b7(A(),a2);
  std::cout << "8" << std::endl;
  B2<A> b8(A(),A());
  std::cout << "9" << std::endl;
  std::cout << std::endl;
  std::cout << "11" << std::endl;
  B1<A> b11(a1,a2);
  std::cout << "12" << std::endl;
  B1<A> b12(a1,inc_a(A()));
  std::cout << "13" << std::endl;
  B1<A> b13(inc_a(A()),a2);
  std::cout << "14" << std::endl;
  B1<A> b14(inc_a(A()),inc_a(A()));
  std::cout << "15" << std::endl;
  B2<A> b15(a1,a2);
  std::cout << "16" << std::endl;
  B2<A> b16(a1,inc_a(A()));
  std::cout << "17" << std::endl;
  B2<A> b17(inc_a(A()),a2);
  std::cout << "18" << std::endl;
  B2<A> b18(inc_a(A()),inc_a(A()));
  std::cout << "19" << std::endl;
}

输出如下:

1
Copy A
Copy A
2
Copy A
Move A
3
Move A
Copy A
4
5
Copy A
Copy A
Move A
Move A
6
Copy A
Move A
Move A
7
Copy A
Move A
Move A
8
9

11
Copy A
Copy A
12
Copy A
Move A
13
Move A
Copy A
14
Move A
Move A
15
Copy A
Copy A
Move A
Move A
16
Move A
Copy A
Move A
Move A
17
Copy A
Move A
Move A
Move A
18
Move A
Move A
Move A
Move A
19

可以看出,B2 中的按值传递方法在所有情况下都会导致每个参数的额外移动,除非参数是纯右值。

如果你想要最好的性能,我建议B1 中的模板方法。这样,您就可以有效地为复制和移动案例提供单独的代码,因此只需要一个副本或一个移动。在按值传递的方法中,至少需要两次移动/复制,但编译器可以在参数位置构造值的纯右值情况除外,在这种情况下,只需要一次移动。

【讨论】:

    【解决方案3】:

    根据您使用的 c++ 编译器,您可以查看“带有变量参数列表的函数”

    这个想法是,您可以将任意数量的参数传递给该方法,并且它只是填充到一个您可以循环的数组中。

    对于 microsoft c++,以下博文可能会有所帮助:

    http://msdn.microsoft.com/en-us/library/fxhdxye9(v=VS.100).aspx http://blogs.msdn.com/b/slippman/archive/2004/02/16/73932.aspx

    【讨论】:

    • 参数的数量是固定的(并且是已知的)。 OP的问题是如何利用移动语义而不是复制参数:)
    • NO C++0x 程序应该使用旧的不安全可变参数,新的 C++0x 方法是可变参数模板,用于具有可变参数计数的 typesafe 函数.除此之外,可变参数与 OP 问题完全无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-24
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    相关资源
    最近更新 更多