【问题标题】:How to support move semantics for a class template that has a reference如何支持具有引用的类模板的移动语义
【发布时间】:2016-04-23 12:22:04
【问题描述】:

情况

我正在设计一个支持移动语义的类模板logiclogic 有一个模板参数Visitor 和一个类型为Visitor& 的引用成员。那是一个库代码。

Users 继承类模板logic 并传递一个自定义访问者,例如my_visitor。自定义访问者可能包含可移动成员。例如,my_visitor 有一个成员 v,其类型为 std::vector

问题

test2()。当我移动 my_logic 时,my_visitor::v 按预期移动。但是,logic<Visitor>::vis 指的是从对象移动。有什么好的方法可以引用移动到的对象吗?

#include <iostream>
#include <vector>

// Library code
template <typename Visitor> // Concept: Visitor should have visit() 
struct logic {
    logic(Visitor& v):vis(v) {}
    void execute() {
        vis.visit();
    }
    // Other APIs

    Visitor& vis;
    // Other member variables...
};

// User code

struct my_visitor {
    my_visitor() { v.push_back(42); }
    void visit() {
        std::cout << "expected 1, actual " << v.size() << std::endl;
    }
    std::vector<int> v;
};

// User inherits all logic's APIs 
struct my_logic : logic<my_visitor> {
    my_logic():logic<my_visitor>(mv) {}
    my_visitor mv;
};

void test1() {
    std::cout << "test1" << std::endl;
    my_logic m;
    m.execute();
}

void test2() {
    std::cout << "test2" << std::endl;
    my_logic m1;
    {
        my_logic m2(std::move(m1)); // logic::vis refers to moved from my_visitor...
        m2.execute();
    }
}


int main() {
    test1();
    test2();
}

【问题讨论】:

  • 你必须为my_logic 实现你的移动构造函数(并且可能删除移动赋值)。

标签: c++ move-semantics


【解决方案1】:

问题是my_logic 有一个成员 (mv) 和对该成员的引用 (vis),您必须确保该引用始终引用同一个成员。使用默认的移动构造函数,新引用 vis 仍然引用 old 成员,然后从该成员中移动。这就是为什么你最终会选择0

 m1.mv  <-----+         m2.mv
  ↑           |
  |           |
  |           |
 m1.vis       +------   m2.vis

正如Jarod 建议的那样,一种解决方案是编写自己的复制/移动构造函数/赋值运算符,以确保m2.vis 指向m2.mv

但是,我建议通过使用 CRTP 并让您的基础 logic 类直接引用派生类来避免额外的引用:

template <class Derived>
struct logic {
    Derived& self() { return static_cast<Derived&>(*this); }

    void execute() {
        self().visit();
    }
};

struct my_visitor : logic<my_visitor) {
    my_visitor() { v.push_back(42); }
    void visit() {
        std::cout << "expected 1, actual " << v.size() << std::endl;
    }
    std::vector<int> v;       
};

这样,只有一种方法可以引用数据 - 所以不会有任何异常。

或者,您可以显式地delete logic 的复制/移动构造函数和赋值运算符。这将要求您为所有派生类型显式编写自己的,但会确保您正确执行。例如:

logic(logic&& ) = delete;

my_logic(my_logic&& rhs)
: logic(mv) // always refer to me!
, mv(std::move(rhs.mv))
{ } 

【讨论】:

【解决方案2】:

使用std::reference_wrapper 而不是原生引用:

std::reference_wrapper 是一个类模板,它将引用包装在可复制、可分配的对象中。它经常被用作在标准容器(如std::vector)中存储引用的机制,这些容器通常不能保存引用。

具体来说,std::reference_wrapper 是一个 CopyConstructibleCopyAssignable 包装器,围绕着对对象的引用或对 T 类型的函数的引用。 std::reference_wrapper 的实例是对象(它们可以被复制或存储在容器中),但它们可以隐式转换为 T&amp;,因此它们可以用作通过引用获取基础类型的函数的参数。

【讨论】:

【解决方案3】:

您必须编写自己的移动/复制构造函数

struct my_logic : logic<my_visitor> {
    my_logic():logic<my_visitor>(mv) {}
    my_visitor mv;

    my_logic(const my_logic& rhs) : logic<my_visitor>(mv), mv(rhs.mv) {}
    my_logic(my_logic&& rhs) : logic<my_visitor>(mv), mv(std::move(rhs.mv)) {}
};

Demo

reference_wrapper,你也可以用类似的方式实现赋值。

【讨论】:

  • 似乎my_logic 的移动构造函数创建了一个新的logic&lt;my_visitor&gt;。类模板逻辑还有其他成员变量(我写了评论)。这是您演示中的第 18 行。我想移动所有成员变量。我刚刚将您的演示代码更新为 coliru.stacked-crooked.com/a/aa6ce7e2506423eb 。我为 logic logic(logic&lt;Visitor&gt;&amp;&amp; other, Visitor&amp; v):vis(v), s(std::move(other.s)) {} 添加了一个构造函数(第 9 行)并调用代码 my_logic(my_logic&& rhs) : logic(std::move(rhs), mv), mv(std::move(rhs.mv )) {}(第 49 行)。然后它按预期工作。谢谢。
  • 我意识到我不能使用这种方式编写移动赋值运算符。因为它需要两个参数。看来我需要为此编写一个不同的成员函数...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-26
  • 1970-01-01
  • 2021-06-16
  • 2020-11-07
  • 2019-08-04
  • 1970-01-01
相关资源
最近更新 更多