如何支持具有引用的类模板的移动语义

时间:2016-04-23 12:22:05

标签: c++ move-semantics

场合

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

用户继承类模板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();
}

3 个答案:

答案 0 :(得分:1)

使用std::reference_wrapper代替原生参考:

  

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

     

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

答案 1 :(得分:1)

问题是my_logic同时包含成员(mv)和对该成员的引用(vis),您必须确保引用始终引用相同的成员(vis)会员。使用默认移动构造函数,新引用0仍然引用成员,然后将其移出。这就是为什么你最终到达 m1.mv <-----+ m2.mv ↑ | | | | | m1.vis +------ m2.vis

的原因
m2.vis

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

但是,我建议只使用CRTP避免额外的引用,让你的基类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)) { } 的复制/移动构造函数和赋值运算符。这将要求您为所有派生类型显式编写自己的类型,但会确保您正确地执行它。例如:

noreturn

答案 2 :(得分:0)

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

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,您也可以用类似的方式实现分配。