在C++0x中传递/移动构造函数的参数

31

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


1
给出代码示例以使其更清晰。(谁知道,也许您根本不需要move构造函数!) - iammilind
2
你能展示一下你现在的代码吗?据我所知,移动构造函数是指“特殊”的构造函数 Foo(Foo&& that),所以我有点困惑你在说什么。 - zneak
@zneak:移动构造函数只会接受同一类的引用,我认为Sid是在谈论将任意“n”个参数传递给构造函数。 @Sid:如果您尝试将每个参数分别传递到不同的函数中(而不是构造函数),那么最坏情况下您将需要2*n个函数,一个用于rvalue,一个用于lvalue,用于每个参数。 - A. K.
1
@zneak,@Aditya:是的,我在谈论将任意 'n' 个参数传递给构造函数。我当时很匆忙,所以标题写得糟糕。我会更正的。 - Opt
3个回答

37
你需要按值传递每一个参数,就像这样:

您需要按值传递每个参数,如下所示:

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;
};

3
+1 很好,在新的世界中,你只需要通过值传递参数,一切都会尽可能地变得更好。那太棒了。你可以加上赋值运算符来增加乐趣吗? - Kerrek SB
2
@Kerrek:如果你需要参数的副本,那么你就使用值传递。但是如果你只需要检查它们(const 引用)或修改它们(引用),那么就不需要使用值传递。我相信你可能已经知道这一点,但是阅读你评论的人可能不知道。 - Benjamin Lindley
3
@Gene: 但是,你现在并没有处理右值。也许有一些右值传递进入了foo构造函数中,它们会被移动到左值s、b和q中。在你的版本中,你直接使用s来构造mS。s 不是 右值,因此既不会发生省略,也不会进行移动构造。你可以通过添加嘈杂的构造函数来验证这一点。http://ideone.com/CRbaA - Benjamin Lindley
@Gene:就像Ben和我之前说的,这里不适用elison。构造函数中没有rvalue。sbq都是lvalue,成员的复制始终是常规复制,除非您明确地移动它(现在我们可以愉快地这样做)。 - GManNickG
@GMan:我刚刚查看了优化汇编代码,当有一个显式的移动mS(move(s))时,编译器(VC2010)创建了一个副本(预期的),并使用移动构造函数初始化成员。在没有显式移动ms(s)的情况下,编译器只是使用源对象的复制构造函数来初始化成员。因此,所有的复制都被省略了。 - Gene Bushuyev
显示剩余19条评论

3

请看以下代码ideone链接

#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中按值传递的方法会导致每个参数在除prvalue以外的所有情况下都需要进行额外的移动。

如果您想获得最佳性能,建议使用B1中的模板方法。这样,您实际上拥有单独的代码来处理复制和移动情况,因此只需要一次复制或一次移动。在按值传递的方法中,至少需要进行两次移动/复制,除了prvalue情况,编译器可以在参数位置构造值,这时只需要一次移动。


-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


3
参数的数量是固定的(并已知)。提问者的问题是如何利用移动语义而不是复制参数 :) - jalf
任何C++0x程序都不应该使用旧的不安全的可变参数,新的C++0x方法是使用可变模板来处理具有可变参数计数的类型安全函数。此外,可变参数与OPs问题完全无关。 - smerlin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接