C++ 使用C+时避免代码重复+;11份副本及;移动
C++11“move”是一个很好的特性,但我发现当与“copy”同时使用时,很难避免代码重复(我们都讨厌这个)。下面的代码是我对一个简单循环队列(不完整)的实现,这两个push()方法除了一行之外几乎相同 我遇到过很多类似的情况。如何避免这种代码重复而不使用宏 ==编辑=== 在这个特定的示例中,重复的代码可以重构出来并放入一个单独的函数中,但有时这种重构不可用或不容易实现C++ 使用C+时避免代码重复+;11份副本及;移动,c++,c++11,move-semantics,code-duplication,C++,C++11,Move Semantics,Code Duplication,C++11“move”是一个很好的特性,但我发现当与“copy”同时使用时,很难避免代码重复(我们都讨厌这个)。下面的代码是我对一个简单循环队列(不完整)的实现,这两个push()方法除了一行之外几乎相同 我遇到过很多类似的情况。如何避免这种代码重复而不使用宏 ==编辑=== 在这个特定的示例中,重复的代码可以重构出来并放入一个单独的函数中,但有时这种重构不可用或不容易实现 #include <cstdlib> #include <utility> template&l
#include <cstdlib>
#include <utility>
template<typename T>
class CircularQueue {
public:
CircularQueue(long size = 32) : size{size} {
buffer = std::malloc(sizeof(T) * size);
}
~CircularQueue();
bool full() const {
return counter.in - counter.out >= size;
}
bool empty() const {
return counter.in == counter.out;
}
void push(T&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{std::forward<T>(data)};
++counter.in;
}
void push(const T& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
new (buffer + offset) T{data};
++counter.in;
}
private:
T* buffer;
long size;
struct {
long in, out;
} counter;
};
#包括
#包括
模板
类循环队列{
公众:
循环队列(长大小=32):大小{size}{
缓冲区=标准::malloc(sizeof(T)*大小);
}
~CircularQueue();
bool full()常量{
返回counter.in-counter.out>=大小;
}
bool empty()常量{
返回counter.in==counter.out;
}
无效推送(T&数据){
if(full()){
抛出无效的{};
}
长偏移=计数器,单位为%size;
新的(缓冲区+偏移量)T{std::forward(data)};
++柜台;柜台;
}
无效推送(常数T和数据){
if(full()){
抛出无效的{};
}
长偏移=计数器,单位为%size;
新的(缓冲区+偏移量)T{data};
++柜台;柜台;
}
私人:
T*缓冲器;
长尺寸;
结构{
久而久之;
}计数器;
};
这里最简单的解决方案是将参数设为转发引用。这样,您只需使用一个功能即可:
template <class U>
void push(U&& data) {
if (full()) {
throw Invalid{};
}
long offset = counter.in % size;
// please note here we construct a T object (the class template)
// from an U object (the function template)
new (buffer + offset) T{std::forward<U>(data)};
++counter.in;
}
使用转发引用的解决方案是一个很好的解决方案。在某些情况下,它变得困难或烦人。作为第一步,使用具有显式类型的接口包装它,然后在cpp文件中将它们发送到模板实现 现在,第一步有时也会失败:如果有N个不同的参数都需要转发到一个容器中,那么这需要一个大小为2^N的接口,并且可能需要跨多个接口层才能实现 为此,我们可以随身携带结束动作,而不是携带或携带特定类型的动作。在最外层的接口上,我们将任意类型转换为那个/那个些操作 它依赖于
std::function
为我们执行类型擦除
由于这被设计为只工作一次(我们从源代码移动),因此我强制使用右值上下文并消除我的状态。我还隐藏了我是std::函数的事实,因为它不遵循这些规则。前言 在向界面添加移动语义支持时引入代码复制是非常烦人的。对于每个函数,您必须进行两个几乎相同的实现:一个从参数复制,另一个从参数移动。如果一个函数有两个参数,那么它甚至不是代码复制,而是代码四次复制:
void Func(const TArg1 &arg1, const TArg2 &arg2); // copies from both arguments
void Func(const TArg1 &arg1, TArg2 &&arg2); // copies from the first, moves from the second
void Func( TArg1 &&arg1, const TArg2 &arg2); // moves from the first, copies from the second
void Func( TArg1 &&arg1, TArg2 &&arg2); // moves from both
在一般情况下,对于一个函数,如果N是参数数,则必须为其补足最多2^N个重载。在我看来,这使得移动语义实际上无法使用。这是C++11最令人失望的特性
这个问题可能会更早发生。让我们来看看下面的代码:
void Func1(const T &arg);
T Func2();
int main()
{
Func1(Func2());
return 0;
}
非常奇怪的是,临时对象被传递到接受引用的函数中。临时对象甚至可能没有地址,例如可以缓存在寄存器中。但是C++允许传递一个const(并且只有const)引用的临时变量。在这种情况下,临时文件的使用寿命延长至参考文件的使用寿命结束。如果没有这条规则,即使在这里,我们也必须进行两种实现:
void Func1(const T& arg);
void Func1(T arg);
我不知道为什么创建了允许在接受引用的位置传递临时对象的规则(如果没有此规则,我们将无法调用复制构造函数来复制临时对象,因此Func1(Func2())
whereFunc1
是void Func1(t arg)
无论如何都无法工作:),但是根据这个规则,我们不必对函数进行两次重载
解决方案#1:完美转发
不幸的是,没有这样一个简单的规则可以使同一个函数不必实现两个重载:一个采用常量左值引用,另一个采用右值引用。相反,完美的转发被设计出来了
template <typename U>
void Func(U &¶m) // despite the fact the parameter has "U&&" type at declaration,
// it actually can be just "U&" or even "const U&", it’s due to
// the template type deducing rules
{
value = std::forward<U>(param); // use move or copy semantic depending on the
// real type of param
}
现在我们可以这样使用它:
void func1(in<std::vector<int>> param);
void func2(in<std::vector<int>> param);
void func3(in<std::vector<int>> param)
{
func1(param); // don't move param into func1 even if original reference
// is rvalue. func1 will always use copy of param, since we
// still need param in this function
// some usage of param
// now we don’t need param
func2(std::move(param)); // move param into func2 if original reference
// is rvalue, or copy param into func2 if original
// reference is const lvalue
}
vec1 = std::move(param1); // moves or copies depending on whether param1 is movable
vec2 = std::move(param2); // moves or copies depending on whether param2 is movable
<>但不幸的是C++不允许代码为> >运算符=<代码>作为全局函数()。但我们可以将此函数重命名为assign
:
template<typename T>
void assign(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
}
同样,这对构造函数也不起作用。我们不能只写:
std::vector<int> vec(std::move(param));
我不知道标准委员会是否考虑了这种移动语义的实现,但是在C++中进行这样的更改可能为时已晚,因为它们会使编译器的ABI与以前的版本不兼容。此外,它还增加了一些运行时开销,可能还有其他我们不知道的问题。
请注意,std::forward可以替换为std::move here。@MikeMB:Not可以,应该是这样。@Nawaz您能解释一下为什么在这种情况下std::move比std::forward好吗?@user416983:因为参数data
绑定到的对象/表达式是右值,即使参数本身是左值。由于实际对象是右值,std::move
是合适的,它将左值转换回右值。阅读有关std::forward
和std::move
及其差异的更多信息,以了解在T&
不是转发引用时使用哪一个。这是一个右值引用。这是因为T
不是函数的模板参数。它是类的一部分,因此它已经推断出类何时被实例化
template <typename T>
class in
{
public:
in (const T& l): v_ (l), rv_ (false) {}
in (T&& r): v_ (r), rv_ (true) {}
bool rvalue () const {return rv_;}
const T& get () const {return v_;}
T&& rget () const {return std::move (const_cast<T&> (v_));}
private:
const T& v_; // original reference
bool rv_; // whether it is rvalue-reference
};
class A
{
public:
void set_vec(in<std::vector<int>> param1, in<std::vector<int>> param2)
{
if (param1.rvalue()) vec1 = param1.rget(); // move if param1 is rvalue
else vec1 = param1.get(); // just copy otherwise
if (param2.rvalue()) vec2 = param2.rget(); // move if param2 is rvalue
else vec2 = param2.get(); // just copy otherwise
}
private:
std::vector<int> vec1, vec2;
};
class in
{
...
in(const in &other): v_(other.v_), rv_(false) {} // always makes parameter not movable
// even if the original reference
// is movable
in( in &&other): v_(other.v_), rv_(other.rv_) {} // makes parameter movable if the
// original reference was is movable
...
};
void func1(in<std::vector<int>> param);
void func2(in<std::vector<int>> param);
void func3(in<std::vector<int>> param)
{
func1(param); // don't move param into func1 even if original reference
// is rvalue. func1 will always use copy of param, since we
// still need param in this function
// some usage of param
// now we don’t need param
func2(std::move(param)); // move param into func2 if original reference
// is rvalue, or copy param into func2 if original
// reference is const lvalue
}
template<typename T>
T& operator=(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
return lhs;
}
vec1 = std::move(param1); // moves or copies depending on whether param1 is movable
vec2 = std::move(param2); // moves or copies depending on whether param2 is movable
template<typename T>
void assign(T &lhs, in<T> rhs)
{
if (rhs.rvalue()) lhs = rhs.rget();
else lhs = rhs.get();
}
assign(vec1, std::move(param1)); // moves or copies depending on whether param1 is movable
assign(vec2, std::move(param2)); // moves or copies depending on whether param2 is movable
std::vector<int> vec(std::move(param));
class vector
{
...
public:
vector(std::in<vector> other); // copy and move constructor
...
}
// The function in ++++C language:
func(std::vector<int> param) // no need to specify const & or &&, param is just parameter.
// it is always reference for complex types (or for types with
// special qualifier that says that arguments of this type
// must be always passed by reference).
{
another_vec = std::move(param); // move parameter if it's movable.
// compiler hides actual rvalue-ness
// of the arguments in its ABI
}