如何允许复制精确结构的C ++类(不只是POD C结构) [英] How to allow copy elision construction for C++ classes (not just POD C structs)

查看:108
本文介绍了如何允许复制精确结构的C ++类(不只是POD C结构)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下代码:

  #include< iostream> 
#include< type_traits>

struct A
{
A(){}
A(const A&){std :: cout< Copy<< std :: endl; }
A(A&&){std :: cout<< 移动<< std :: endl; }
};

template< class T>
struct B
{
T x;
};

#define MAKE_B(x)B< decltype(x)> {x}

template< class T>
B< T> make_b(T& x)
{
return B< T> {std :: forward< T>(x)};
}

int main()
{
std :: cout< Macro make b<< std :: endl;
auto b1 = MAKE_B(A());
std :: cout<< Non-macro make b<< std :: endl;
auto b2 = make_b(A());
}

这将输出以下内容:


宏建立b

非宏建立b

移动


注意,b1的构造没有移动,但是b2的构造需要移动。



我还需要键入扣除,因为 A 在现实生活中的使用可能是一个复杂的类型,很难写明确。我还需要能够嵌套调用(即 make_c(make_b(A())))。



这种功能是否可能?



进一步的想法:


N3290 Final C ++ 0x草稿页面284:



这种复制/移动操作的缺失,
称为复制错误,在以下情况下允许在
中:



当临时类对象
未绑定到引用
(12.2)
将被复制/移动到具有相同的cv非限定的
类型的类
对象,复制/移动操作可以通过构造临时
对象直接进入
的目标copy / move


不幸的是,将函数参数复制(和移动)到函数结果(包括构造函数),因为那些临时函数绑定到引用(当通过引用传递时)或不再临时(当通过值传递时)。看来创建一个复合对象时,要删除所有副本的唯一方法是将它创建为一个聚合。但是,聚合有一些限制,例如要求所有成员都是公共的,没有用户定义的构造函数。



我认为C ++不允许优化POD C-structs聚合构造,但不允许对非POD C ++类构造进行相同的优化。



有任何方法允许复制/移动精确?



这个结构允许复制被省略非POD类型。我从 DavidRodríguez的答案。它需要C ++ 11 lambdas。在下面的例子中,我改变了 make_b 来接受两个参数,使事情变得更简单。没有任何调用任何移动或复制构造函数。

  #include< iostream& 
#include< type_traits>

struct A
{
A(){}
A(const A&){std :: cout< 复制<< std :: endl; }
A(A&&){std :: cout<< 移动<< std :: endl; }
};

template< class T>
class B
{
public:
template< class LAMBDA1,class LAMBDA2>
B(const LAMBDA1& f1,const LAMBDA2& f2):x1(f1()),x2(f2())
{
std :: cout
< ; 我是一个不平凡的,因此不是POD.\\\

< 我也有私人数据成员,所以绝对不是一个POD!\\\
;
}
private:
T x1;
T x2;
};

#define DELAY(x)[&] {return x; }

#define MAKE_B(x1,x2)make_b(DELAY(x1),DELAY(x2))

template< class LAMBDA1,class LAMBDA2>
auto make_b(const LAMBDA1& f1,const LAMBDA2& f2) - > B< decltype(f1())>
{
return B< decltype(f1())>(f1,f2);
}

int main()
{
auto b1 = MAKE_B(A(),A());
}

如果任何人知道如何更整洁地实现这一点,



以上讨论:到以下问题:



可以优化从临时创建复合对象吗?

避免对表达式模板使用#define

如果在构建复合对象时删除不必要的副本安东尼已经提到,标准禁止复制从一个函数的参数到相同函数的返回。驱动该决定的基本原理是复制精确(和移动精确)是一种优化,通过该优化,程序中的两个对象被合并到同一存储器位置,即,通过使两个对象都是一个而使副本被省略。 (部分)标准报价如下,其后是允许复制删除的一组情况,不包括该特定情况。



病例不同?区别基本上是在原始和复制的对象之间有一个函数调用的事实,函数调用意味着有额外的约束要考虑,特别是调用约定。



给定一个函数 T foo(T)和用户调用 T x = foo(T(param)); code>,在一般情况下,使用单独的编译,编译器将在调用约定需要第一个参数的位置创建一个对象 $ tmp1 。然后它将调用该函数并从return语句中初始化 x 。这是复制精确的第一次机会:通过在返回的临时位置小心放置 x x foo 返回的对象成为单个对象,并且该副本被省略。到现在为止还挺好。问题是调用约定一般不会有返回的对象和参数在同一位置,因此, $ tmp1



没有看到函数定义,编译器不可能知道参数的唯一目的是函数是作为返回语句,因此它不能省略额外的副本。可以说,如果函数 inline ,那么编译器会缺少额外的信息来理解用于调用函数的临时函数,返回值和 x 是单个对象。问题是,如果代码实际上是内联的,那么特定的副本只能被省略(不仅如果它被标记为 inline ,但实际上是内联的)如果需要函数调用,则不能省略副本。如果标准允许在代码内联时省略该副本,则意味着由于编译器而不是用户代码,程序的行为会不同 - inline 关键字不强制内联,它只意味着相同函数的多个定义不表示违反ODR。



请注意,如果变量是 T foo(){T tmp;}在函数内部创建(与传递给它相比) ...; return tmp; } T x = foo(); ,则两个副本都可以省略:没有限制,因为必须创建 tmp 不是函数的输入或输出参数,因此编译器能够将它重定位到任何位置,包括返回类型的位置,并且在调用侧, x 可以前面的例子仔细地放在同一个返回语句的位置,这基本上意味着 tmp ,返回语句和 x 可以是单个对象。



由于你的特殊问题,如果你诉诸宏,代码是内联的,没有对对象和但是如果你添加一个函数,你不能从参数中复制到return语句,所以只是避免它。而不是使用一个模板来移动对象,创建一个将会构造对象的模板:

  template< typename T,typename ... Args> 
T create(Args ... x){
return T(x ...);
}

编译器可以省略该副本。



请注意,我没有处理移动建设,因为你似乎关心甚至移动建设的成本,即使我相信你在错误的树上吠叫。考虑到一个激励的真实用例,我非常肯定,这里的人会想出一些有效的想法。



12.8 / 31


当满足特定条件时,允许实现省略类对象的复制/移动构造,即使复制/移动构造函数和/或对象的析构函数有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为简单的两种不同的引用同一对象的方式,并且该对象的销毁发生在当两个对象将被无需优化即可销毁。



Consider the following code:

#include <iostream>
#include <type_traits>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
};

template <class T>
struct B
{
  T x;
};

#define MAKE_B(x) B<decltype(x)>{ x }

template <class T>
B<T> make_b(T&& x)
{
  return B<T> { std::forward<T>(x) };
}

int main()
{
  std::cout << "Macro make b" << std::endl;
  auto b1 = MAKE_B( A() );
  std::cout << "Non-macro make b" << std::endl;
  auto b2 = make_b( A() );
}

This outputs the following:

Macro make b
Non-macro make b
Move

Note that b1 is constructed without a move, but the construction of b2 requires a move.

I also need to type deduction, as A in real life usage may be a complex type which is difficult to write explicitly. I also need to be able to nest calls (i.e. make_c(make_b(A()))).

Is such a function possible?

Further thoughts:

N3290 Final C++0x draft page 284:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances:

when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

Unfortunately this seems that we can't elide copies (and moves) of function parameters to function results (including constructors) as those temporaries are either bound to a reference (when passed by reference) or no longer temporaries (when passed by value). It seems the only way to elide all copies when creating a composite object is to create it as an aggregate. However, aggregates have certain restrictions, such as requiring all members be public, and no user defined constructors.

I don't think it makes sense for C++ to allow optimizations for POD C-structs aggregate construction but not allow the same optimizations for non-POD C++ class construction.

Is there any way to allow copy/move elision for non-aggregate construction?

My answer:

This construct allows for copies to be elided for non-POD types. I got this idea from David Rodríguez's answer below. It requires C++11 lambdas. In this example below I've changed make_b to take two arguments to make things less trivial. There are no calls to any move or copy constructors.

#include <iostream>
#include <type_traits>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
};

template <class T>
class B
{
public:
  template <class LAMBDA1, class LAMBDA2>
  B(const LAMBDA1& f1, const LAMBDA2& f2) : x1(f1()), x2(f2()) 
  { 
    std::cout 
    << "I'm a non-trivial, therefore not a POD.\n" 
    << "I also have private data members, so definitely not a POD!\n";
  }
private:
  T x1;
  T x2;
};

#define DELAY(x) [&]{ return x; }

#define MAKE_B(x1, x2) make_b(DELAY(x1), DELAY(x2))

template <class LAMBDA1, class LAMBDA2>
auto make_b(const LAMBDA1& f1, const LAMBDA2& f2) -> B<decltype(f1())>
{
  return B<decltype(f1())>( f1, f2 );
}

int main()
{
  auto b1 = MAKE_B( A(), A() );
}

If anyone knows how to achieve this more neatly I'd be quite interested to see it.

Previous discussion:

This somewhat follows on from the answers to the following questions:

Can creation of composite objects from temporaries be optimised away?
Avoiding need for #define with expression templates
Eliminating unnecessary copies when building composite objects

解决方案

As Anthony has already mentioned, the standard forbids copy elision from the argument of a function to the return of the same function. The rationale that drives that decision is that copy elision (and move elision) is an optimization by which two objects in the program are merged into the same memory location, that is, the copy is elided by having both objects be one. The (partial) standard quote is below, followed by a set of circumstances under which copy elision is allowed, which do not include that particular case.

So what makes that particular case different? The difference is basically that the fact that there is a function call between the original and the copied objects, and the function call implies that there are extra constraints to consider, in particular the calling convention.

Given a function T foo( T ), and a user calling T x = foo( T(param) );, in the general case, with separate compilation, the compiler will create an object $tmp1 in the location that the calling convention requires the first argument to be. It will then call the function and initialize x from the return statement. Here is the first opportunity for copy elision: by carefully placing x on the location where the returned temporary is, x and the returned object from foo become a single object, and that copy is elided. So far so good. The problem is that the calling convention in general will not have the returned object and the parameter in the same location, and because of that, $tmp1 and x cannot be a single location in memory.

Without seeing the function definition the compiler cannot possibly know that the only purpose of the argument to the function is to serve as return statement, and as such it cannot elide that extra copy. It can be argued that if the function is inline then the compiler would have the missing extra information to understand that the temporary used to call the function, the returned value and x are a single object. The problem is that that particular copy can only be elided if the code is actually inlined (not only if it is marked as inline but actually inlined) If a function call is required, then the copy cannot be elided. If the standard allowed that copy to be elided when the code is inlined, it would imply that the behavior of a program would differ due to the compiler and not user code --the inline keyword does not force inlining, it only means that multiple definitions of the same function do not represent a violation of the ODR.

Note that if the variable was created inside the function (as compared to passed into it) as in: T foo() { T tmp; ...; return tmp; } T x = foo(); then both copies can be elided: There is no restriction as of where tmp has to be created (it is not an input or output parameter to the function so the compiler is able to relocate it anywhere, including the location of the returned type, and on the calling side, x can as in the previous example be carefully located in the location of that same return statement, which basically means that tmp, the return statement and x can be a single object.

As of your particular problem, if you resort to a macro, the code is inlined, there are no restrictions on the objects and the copy can be elided. But if you add a function, you cannot elide the copy from the argument to the return statement. So just avoid it. Instead of using a template that will move the object, create a template that will construct an object:

template <typename T, typename... Args>
T create( Args... x ) {
   return T( x... );
}

And that copy can be elided by the compiler.

Note that I have not dealt with move construction, as you seem concerned on the cost of even move construction, even though I believe that you are barking at the wrong tree. Given a motivating real use case, I am quite sure that people here will come up with a couple of efficient ideas.

12.8/31

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.

这篇关于如何允许复制精确结构的C ++类(不只是POD C结构)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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