如何支持具有引用的 class 模板的移动语义
How to support move semantics for a class template that has a reference
情况
我正在设计一个支持移动语义的 class 模板 logic
。 logic
有一个模板参数 Visitor
和一个类型为 Visitor&
的引用成员。那是图书馆代码。
Users 继承了 class 模板 logic
并传递了自定义访问者,例如 my_visitor
。自定义访问者可能包含可移动成员。例如,my_visitor
有一个类型为 std::vector
.
的成员 v
问题
参见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();
}
使用 std::reference_wrapper
而不是本地引用:
std::reference_wrapper
is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (like std::vector
) which cannot normally hold references.
Specifically, std::reference_wrapper
is a CopyConstructible and CopyAssignable wrapper around a reference to object or reference to function of type T
. Instances of std::reference_wrapper
are objects (they can be copied or stored in containers) but they are implicitly convertible to T&
, so that they can be used as arguments with the functions that take the underlying type by reference.
您必须编写自己的 move/copy 构造函数
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)) {}
};
和reference_wrapper
一样,你也可以用类似的方式实现赋值。
问题是 my_logic
有一个成员 (mv
) 和对该成员的引用 (vis
),您必须确保引用始终指向同一个成员。使用默认的移动构造函数,新引用 vis
仍然引用 old 成员,然后从中移动。这就是为什么你最终在 0
:
m1.mv <-----+ m2.mv
↑ |
| |
| |
m1.vis +------ m2.vis
一个解决方案是,正如 所建议的那样,编写您自己的 copy/move constructors/assignment 运算符以确保 m2.vis
指向 m2.mv
。
但是,我建议通过仅使用 CRTP 并让您的基础 logic
class 直接引用派生的基础来避免额外的引用:
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
的 copy/move 构造函数和赋值运算符。这将要求您为所有派生类型显式编写您自己的类型,但会确保您正确地完成了。例如:
logic(logic&& ) = delete;
my_logic(my_logic&& rhs)
: logic(mv) // always refer to me!
, mv(std::move(rhs.mv))
{ }
情况
我正在设计一个支持移动语义的 class 模板 logic
。 logic
有一个模板参数 Visitor
和一个类型为 Visitor&
的引用成员。那是图书馆代码。
Users 继承了 class 模板 logic
并传递了自定义访问者,例如 my_visitor
。自定义访问者可能包含可移动成员。例如,my_visitor
有一个类型为 std::vector
.
v
问题
参见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();
}
使用 std::reference_wrapper
而不是本地引用:
std::reference_wrapper
is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (likestd::vector
) which cannot normally hold references.Specifically,
std::reference_wrapper
is a CopyConstructible and CopyAssignable wrapper around a reference to object or reference to function of typeT
. Instances ofstd::reference_wrapper
are objects (they can be copied or stored in containers) but they are implicitly convertible toT&
, so that they can be used as arguments with the functions that take the underlying type by reference.
您必须编写自己的 move/copy 构造函数
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)) {}
};
和reference_wrapper
一样,你也可以用类似的方式实现赋值。
问题是 my_logic
有一个成员 (mv
) 和对该成员的引用 (vis
),您必须确保引用始终指向同一个成员。使用默认的移动构造函数,新引用 vis
仍然引用 old 成员,然后从中移动。这就是为什么你最终在 0
:
m1.mv <-----+ m2.mv
↑ |
| |
| |
m1.vis +------ m2.vis
一个解决方案是,正如 m2.vis
指向 m2.mv
。
但是,我建议通过仅使用 CRTP 并让您的基础 logic
class 直接引用派生的基础来避免额外的引用:
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
的 copy/move 构造函数和赋值运算符。这将要求您为所有派生类型显式编写您自己的类型,但会确保您正确地完成了。例如:
logic(logic&& ) = delete;
my_logic(my_logic&& rhs)
: logic(mv) // always refer to me!
, mv(std::move(rhs.mv))
{ }