2

我正在开发一个简单的包装模板类,该类在调用特殊成员函数时进行记录。这些函数不能默认,因为它们执行额外的日志相关任务。

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

不幸的是,当包装类型是右值引用时,复制构造函数无法编译并显示以下错误消息:

错误:无法将“int”左值绑定到“int&&”</p>

原因是隐式复制构造函数对于右值引用成员的行为会有所不同:

[class.copy 12.8/15] 非联合类的隐式定义的复制/移动构造函数X执行其基类和成员的成员复制/移动。[...]或者x是构造函数的参数,或者对于移动构造函数,是一个引用参数的 xvalue。每个基本或非静态数据成员都以适合其类型的方式复制/移动:

  • 如果成员是一个数组,则每个元素都直接使用 ; 的相应子对象进行初始化x
  • 如果成员 m 具有右值引用类型T&&,则使用static_cast<T&&>(x.m);直接初始化它。
  • 否则,基数或成员将直接用 的相应基数或成员初始化x

这让我想到了我的问题:即使在使用rvalue-references作为成员时,如何编写一个表现为隐式定义的copy-constructor的通用复制构造函数。

对于这种特殊情况,我可以为rvalue-references添加一个额外的特化。但是,我正在寻找一种不限于单个成员并且不引入代码重复的通用解决方案。

4

2 回答 2

3

这里是龙。

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

表达式other.value_是类型的左值T const,例如int&int&&int const

  1. 如果T == int&&,则需要执行move, 因为表达式是左值。move相当于 a ,static_cast<int&&>所以你也可以static_cast直接做。

  2. 如果T == int&,则不需要强制转换。

  3. 如果T == int,则不需要强制转换。

对于定义为的复制ctor:

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

应用于第三种情况,这被定义为临时的引入,并可能导致额外的复制/移动,尽管我认为它可以被省略。

不依赖复制/移动省略的解决方案是引入 a 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);
}

这类似于std::forward,但也支持“转发”非引用类型。


龙在哪里?

[class.copy]/11 指定:

一个类的默认复制/移动构造函数X定义为已删除,如果X有:

  • [...]
  • 对于复制构造函数,右值引用类型的非静态数据成员
  • [...]

右值引用通常绑定到 xvalue 或 prvalue,即绑定到引用“接近其生命周期结束”的对象的表达式。由于生命周期不会通过函数边界延长,因此允许这样的“复制”很容易出错。

于 2013-09-27T01:08:43.473 回答
0

您可以为右值引用编写一个专业化:

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

但我真的不认为你想logger::_value成为一个右值参考......

编辑

虽然我觉得这不是一个糟糕的解决方案,因为它是所有右值引用的通用解决方法,但这是另一个没有文字专业化的选项:

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

rvalue_remover像这样的东西在哪里:

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

我很确定这已经在 c++11 中定义了,但我没有在这里安装它,所以我不记得名字了。

编辑2

啊! 找到了!它被称为std::remove_reference<T>::type,并在#include <type_traits>

于 2013-09-26T23:58:53.563 回答