C ++ 0x rvalue references - lvalues-rvalue binding [英] C++0x rvalue references - lvalues-rvalue binding

查看:342
本文介绍了C ++ 0x rvalue references - lvalues-rvalue binding的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是对
的后续问题 http://stackoverflow.com / question / 2748866 / c0x-rvalue-references-and-temporaries



在上一个问题中,我问这个代码应该如何工作:

  void f(const std :: string&); // less efficient 
void f(std :: string&&); //更高效

void g(const char * arg)
{
f(arg);
}

似乎移动超载应该被调用,这种情况发生在GCC而不是MSVC(或者MSVC的Intellisense中使用的EDG前端)。



这个代码怎么办?

  void f(std :: string&&); // NB:No const string&重载提供

void g1(const char * arg)
{
f(arg);
}
void g2(const std :: string& arg)
{
f(arg);
}

看来,根据我上一个问题的答案,函数 g1 是合法的(并且被GCC 4.3-4.5接受,但不被MSVC接受)。然而,GCC和MSVC都拒绝 g2 ,因为条款13.3.3.1.4 / 3禁止lvalue绑定到rvalue ref参数。我理解这背后的理由 - 这在N2831解决右值引用的安全问题中解释。我也认为GCC可能正在实施这个条款作为该文件的作者的意图,因为GCC的原始补丁是由作者之一(Doug Gregor)。



<然而,我不这样是很直观。对我来说,(a) const string& 在概念上更接近于 string&& code> const char * ,并且(b)编译器可以在 g2 中创建一个临时字符串, :

  void g2(const std :: string& arg)
{
f(std :: string(arg));
}

事实上,有时复制构造函数被认为是一个隐式转换操作符。在语法上,这是通过复制构造函数的形式建议的,并且标准甚至在第13.3.3.1.2 / 4节中特别提到,其中用于派生基本转换的复制构造函数被赋予比其他用户定义的转换:


将类型类型的表达式转换为相同类别类型的转换为精确匹配排名和转换
类型的表达式的类型的基类给予转换排名,尽管事实是为这些情况调用
a copy / move构造函数(即,用户定义的转换函数)。 p>

(我假设这是在派生类传递给 void h(Base)

动机



我提出这个问题的动机是类似于 http://stackoverflow.com/questions/2696156/how-to-reduce-redundant-code-when-adding-new-c0x-rvalue-reference-operator-over (如何减少冗余代码时添加新的c ++ 0x右值引用运算符重载)。



如果你有一个函数接受一些潜在可移动的参数,并且如果它可以移动它们(例如工厂函数/构造函数: Object create_object(string,vector< string>,string)等),并且想要适当地移动或复制每个参数,你很快就开始写 代码。



如果参数类型是可移动的,那么只需编写一个接受参数的版本,如上所示。但是如果参数是(传统的)不可移动但可交换的类a la C ++ 03,并且您不能更改它们,那么编写右值引用重载更有效。



因此,如果lvalues通过隐式副本绑定到右值,那么你可以只写一个重载,如 create_object(legacy_string&&&&&&&&&&&&& ;&),它或多或少地工作,像提供rvalue / lvalue引用重载的所有组合 - 实际的参数将被复制然后绑定到参数,实际参数是rvalues

我知道这实际上和接受可移动类型的值的参数是一样的,例如C ++ 0x std :: string和std :: vector(保存move constructor在概念上被调用的次数)。然而,对于可复制的,但不可移动的类型,它包括具有明确定义的复制构造函数的所有C ++ 03类是不相同的。考虑这个例子:

  class legacy_string {legacy_string(const legacy_string&); }; //在一个标题中定义;不可修改。 

void f(legacy_string s1,legacy_string s2); // A * new *(C ++ 0x)函数想要从其参数中移动,尽可能避免复制
void g()//一个C ++ 0x函数以及
{
legacy_string x(/ * initialization * /);
legacy_string y(/ * initialization * /);

f(std :: move(x),std :: move(y));
}

如果 g f ,则会复制 x y 看不到编译器如何移动它们。如果 f 被声明为使用 legacy_string&& 参数,它可以避免调用者明确调用 std :: move



问题



我的问题是:


  1. 这是对标准的有效解释吗?

  2. 这是否有直观的意义?

  3. 这个想法有问题吗?我没有看到它似乎你可以得到的副本被静静地创建,当不是完全预期,但这是在C ++ 03的地方的现状,而且,它会使一些重载是可行的,当他们目前
  4. 这是一个显着的改进,它是值得的,例如一个实验补丁GCC?


解决方案

我在这个问题上看不到你的观点。如果你有一个类是可移动的,您只需要一个 T 版本:

  struct A {
T t;
A(T t):t(move(t)){}
};

$ b b

如果类是传统的,但是有一个高效的 swap ,你可以编写swap版本,或者你可以回退到 const T& 方式

  struct A {
T t;
A(T t){swap(this-> t,t); }
};

关于交换版本,我宁愿用 const T& / code>方式而不是交换。交换技术的主要优点是异常安全,并且将副本移动到更接近调用者,以便它可以优化临时副本的副本。但是如果你只是构造对象,你有什么要保存 如果构造函数很小,编译器可以查看它,并可以优化离开副本。

  struct A {
T t;
A(T const& t):t(t){}
};

对我来说,自动将字符串lvalue转换为自身的右值副本只是绑定到右值引用。右值引用说它绑定到右值。但是如果你尝试绑定到相同类型的左值,它更好失败。介绍隐藏的副本,以允许这听起来不对我,因为当人们看到 X&& ,并且你传递 X lvalue,我敢打赌大多数期望没有副本,并且绑定是直接,如果它的工作。更好地失败,使用户可以修复他/她的代码。


This is a follow-on question to http://stackoverflow.com/questions/2748866/c0x-rvalue-references-and-temporaries

In the previous question, I asked how this code should work:

void f(const std::string &); //less efficient
void f(std::string &&); //more efficient

void g(const char * arg)
{
    f(arg);
}

It seems that the move overload should probably be called because of the implicit temporary, and this happens in GCC but not MSVC (or the EDG front-end used in MSVC's Intellisense).

What about this code?

void f(std::string &&); //NB: No const string & overload supplied

void g1(const char * arg)
{
     f(arg);
}
void g2(const std::string & arg)
{
    f(arg);
}

It seems that, based on the answers to my previous question that function g1 is legal (and is accepted by GCC 4.3-4.5, but not by MSVC). However, GCC and MSVC both reject g2 because of clause 13.3.3.1.4/3, which prohibits lvalues from binding to rvalue ref arguments. I understand the rationale behind this - it is explained in N2831 "Fixing a safety problem with rvalue references". I also think that GCC is probably implementing this clause as intended by the authors of that paper, because the original patch to GCC was written by one of the authors (Doug Gregor).

However, I don't this is quite intuitive. To me, (a) a const string & is conceptually closer to a string && than a const char *, and (b) the compiler could create a temporary string in g2, as if it were written like this:

void g2(const std::string & arg)
{
    f(std::string(arg));
}

Indeed, sometimes the copy constructor is considered to be an implicit conversion operator. Syntactically, this is suggested by the form of a copy constructor, and the standard even mentions this specifically in clause 13.3.3.1.2/4, where the copy constructor for derived-base conversions is given a higher conversion rank than other user-defined conversions:

A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy/move constructor (i.e., a user-defined conversion function) is called for those cases.

(I assume this is used when passing a derived class to a function like void h(Base), which takes a base class by value.)

Motivation

My motivation for asking this is something like the question asked in http://stackoverflow.com/questions/2696156/how-to-reduce-redundant-code-when-adding-new-c0x-rvalue-reference-operator-over ("How to reduce redundant code when adding new c++0x rvalue reference operator overloads").

If you have a function that accepts a number of potentially-moveable arguments, and would move them if it can (e.g. a factory function/constructor: Object create_object(string, vector<string>, string) or the like), and want to move or copy each argument as appropriate, you quickly start writing a lot of code.

If the argument types are movable, then one could just write one version that accepts the arguments by value, as above. But if the arguments are (legacy) non-movable-but-swappable classes a la C++03, and you can't change them, then writing rvalue reference overloads is more efficient.

So if lvalues did bind to rvalues via an implicit copy, then you could write just one overload like create_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&) and it would more or less work like providing all the combinations of rvalue/lvalue reference overloads - actual arguments that were lvalues would get copied and then bound to the arguments, actual arguments that were rvalues would get directly bound.

Clarification/edit: I realize this is virtually identical to accepting arguments by value for movable types, like C++0x std::string and std::vector (save for the number of times the move constructor is conceptually invoked). However, it is not identical for copyable, but non-movable types, which includes all C++03 classes with explicitly-defined copy constructors. Consider this example:

class legacy_string { legacy_string(const legacy_string &); }; //defined in a header somewhere; not modifiable.

void f(legacy_string s1, legacy_string s2); //A *new* (C++0x) function that wants to move from its arguments where possible, and avoid copying
void g() //A C++0x function as well
{
    legacy_string x(/*initialization*/);
    legacy_string y(/*initialization*/);

    f(std::move(x), std::move(y));
}

If g calls f, then x and y would be copied - I don't see how the compiler can move them. If f were instead declared as taking legacy_string && arguments, it could avoid those copies where the caller explicitly invoked std::move on the arguments. I don't see how these are equivalent.

Questions

My questions are then:

  1. Is this a valid interpretation of the standard? It seems that it's not the conventional or intended one, at any rate.
  2. Does it make intuitive sense?
  3. Is there a problem with this idea that I"m not seeing? It seems like you could get copies being quietly created when that's not exactly expected, but that's the status quo in places in C++03 anyway. Also, it would make some overloads viable when they're currently not, but I don't see it being a problem in practice.
  4. Is this a significant enough improvement that it would be worth making e.g. an experimental patch for GCC?

解决方案

I don't quite see your point in this question. If you have a class that is movable, then you just need a T version:

struct A {
  T t;
  A(T t):t(move(t)) { }
};

And if the class is traditional but has an efficient swap you can write the swap version or you can fallback to the const T& way

struct A {
  T t;
  A(T t) { swap(this->t, t); }
};

Regarding the swap version, I would rather go with the const T& way instead of that swap. The main advantage of the swap technique is exception safety and is to move the copy closer to the caller so that it can optimize away copies of temporaries. But what do you have to save if you are just constructing the object anyway? And if the constructor is small, the compiler can look into it and can optimize away copies too.

struct A {
  T t;
  A(T const& t):t(t) { }
};

To me, it doesn't seem right to automatically convert a string lvalue to a rvalue copy of itself just to bind to a rvalue reference. An rvalue reference says it binds to rvalue. But if you try binding to an lvalue of the same type it better fails. Introducing hidden copies to allow that doesn't sound right to me, because when people see a X&& and you pass a X lvalue, I bet most will expect that there is no copy, and that binding is directly, if it works at all. Better fail out straight away so the user can fix his/her code.

这篇关于C ++ 0x rvalue references - lvalues-rvalue binding的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆