简体   繁体   English

具有右值引用成员的泛型复制构造函数

[英]Generic copy-constructor with rvalue-reference members

I'm working on a simple wrapper template class that logs when special member functions are called. 我正在研究一个简单的包装器模板类,该类在调用特殊成员函数时记录日志。 These functions cannot be defaulted since they perform additional logging related tasks. 这些功能无法默认,因为它们执行与日志记录有关的其他任务。

template <typename T>
struct logger {
    logger(T const& value) : value_(value) { /*...log...*/ }
    logger(T&& value) : value_(std::move(value)) { /*...log...*/ }
    logger(logger const& other) : value_(other.value_) { /*...log...*/ }
    logger(logger&& other) : value_(std::move(other.value_)) { /*...log...*/ }

    T value_;
};

Unfortunately, when the wrapped type is an rvalue-reference the copy-constructor fails to compile with the following error message: 不幸的是,当包装的类型是右值引用时复制构造函数无法编译并显示以下错误消息:

error: cannot bind 'int' lvalue to 'int&&' 错误:无法将“ int”左值绑定到“ int &&”

The reason is that an implicit copy constructor will behave somewhat different for an rvalue-reference member: 原因是隐式副本构造函数对于右值引用成员的行为有所不同:

[class.copy 12.8/15] The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members. [class.copy 12.8 / 15]非工会类X的隐式定义的复制/移动构造函数对其基本和成员执行成员式复制/移动。 [...] Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. [...]x为构造函数的参数,或者对于move构造函数,为引用该参数的xvalue。 Each base or non-static data member is copied/moved in the manner appropriate to its type: 每个基本或非静态数据成员都按照适合其类型的方式进行复制/移动:

  • if the member is an array, each element is direct-initialized with the corresponding subobject of x ; 如果成员是数组,则用x的相应子对象直接初始化每个元素;
  • if a member m has rvalue reference type T&& , it is direct-initialized with static_cast<T&&>(xm) ; 如果成员m具有右值引用类型T&& ,则使用static_cast<T&&>(xm)直接初始化它;
  • otherwise, the base or member is direct-initialized with the corresponding base or member of x . 否则,将使用x的相应基数或成员直接初始化基数或成员。

Which brings me to my question: how does one write a generic copy-constructor that behaves as an implicitly defined copy-constructor , even when working with rvalue-references as members. 这让我想到了一个问题:即使使用右值引用作为成员,如何编写一个表现为隐式定义的复制构造函数的通用复制构造函数

For this particular case, I could add an additional specialization for rvalue-references . 对于这种特殊情况,我可以为rvalue-references添加额外的专业化。 However, I'm looking for a generic solution that doesn't restrict to single members and does not introduce code duplication. 但是,我正在寻找一种通用解决方案,该解决方案不限于单个成员,也不引入代码重复。

Here be dragons. 这是龙。

logger(logger const& other) : value_(other.value_)

The expression other.value_ is an lvalue of type T const , eg int& , int&& or int const . 表达式other.value_是类型为T const的左值,例如int&int&&int const

  1. If T == int&& , you need to do a move , as the expression is an lvalue. 如果T == int&& ,则需要执行move ,因为表达式是一个左值。 The move is equivalent to a static_cast<int&&> , so you could do the static_cast directly as well. move等效于static_cast<int&&> ,因此您也可以直接执行static_cast

  2. If T == int& , no cast is required. 如果T == int& ,则不需要强制转换。

  3. If T == int , no cast is required. 如果T == int ,则不需要强制转换。

For a copy ctor defined as: 对于定义为:

logger(logger const& other) : value_(static_cast<T>(other.value_)) {/*...*/}

Applied to the third case, this is defined as the introduction of a temporary, and could result in an additional copy/move, although I think it can&will be elided. 适用于第三种情况,这被定义为引入临时文件,并且可能导致额外的复制/移动,尽管我认为可以并将会删除。

A solution without relying on the copy/move elision is to introduce a weird_cast , that yields the desired type in any case: 一个不依赖于复制/移动weird_cast的解决方案是引入weird_cast ,在任何情况下都会产生所需的类型:

#include <type_traits>

template<class T, class U>
typename std::enable_if<std::is_reference<T>{}, T>::type
weird_cast(U& p)
{
    return static_cast<T>(p);
}

template<class T, class U>
typename std::enable_if<not std::is_reference<T>{}, T const&>::type
weird_cast(U const& p)
{
    return p;
}

int main()
{
    int           o = 42;
    int &        lo = o;
    int &&       ro = std::move(o);
    int const   lco = o;

    int&& r = weird_cast<int&&>(ro);
    int&  l = weird_cast<int& >(lo);
    int   d = weird_cast<int  >(lco);
}

This is similar to std::forward , but also supports "forwarding" non-reference types. 这类似于std::forward ,但也支持“转发”非引用类型。


Where are the dragons? 龙在哪里?

[class.copy]/11 specifies: [class.copy] / 11指定:

A defaulted copy/move constructor for a class X is defined as deleted if X has: 如果X具有以下条件,则将默认的类X复制/移动构造函数定义为已删除:

  • [...] [...]
  • for the copy constructor, a non-static data member of rvalue reference type 对于副本构造函数,右值引用类型的非静态数据成员
  • [...] [...]

An rvalue reference is typically bound to an xvalue or prvalue, ie to an expression referring to an object that is "near the end of its lifetime". 右值引用通常绑定到x值或pr值,即绑定到引用“在其生命周期即将结束时”的对象的表达式。 As lifetime doesn't get extended through function boundaries, it would be error prone to allow such a "copying". 由于生存期不会通过函数边界扩展,因此允许进行这种“复制”很容易出错。

You could write a specialization for rvalue-references: 您可以为rvalue-references编写专门化的代码:

template<typename T>
struct logger<T&&>{
  ...
};

But really I don't think you want logger::_value to be a rvalue reference... 但实际上我不认为您希望logger::_value成为右值引用...

Edit 编辑

Although I feel this isn't a bad solution, as it's a GENERAL workaround for ALL rvalue references, here's another option without literal specialization: 尽管我觉得这不是一个不好的解决方案,因为它是所有右值引用的通用解决方法,但这是没有字面专门化的另一种选择:

template<typename TT>
struct logger{
  typedef typename rvalue_remover<TT>::value T;
  //your previous code here
};

Where rvalue_remover is something like this: 其中rvalue_remover是这样的:

template<typename T>struct rvalue_remover{typedef T value;};
template<typename T>struct rvalue_remover<T&&>{typedef T value;};

I'm pretty sure this is already defined in c++11, but I don't have it installed here so I don't remember the name. 我很确定这已经在c ++ 11中定义,但是我没有在这里安装它,所以我不记得这个名字了。

Edit2 EDIT2

Ah! 啊! found it! 找到了! It's called std::remove_reference<T>::type , and declared in #include <type_traits> 它称为std::remove_reference<T>::type ,并在#include <type_traits>声明

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

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