C++ 使用C+时避免代码重复+;11份副本及;移动

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

C++11“move”是一个很好的特性,但我发现当与“copy”同时使用时,很难避免代码重复(我们都讨厌这个)。下面的代码是我对一个简单循环队列(不完整)的实现,这两个push()方法除了一行之外几乎相同

我遇到过很多类似的情况。如何避免这种代码重复而不使用宏

==编辑===

在这个特定的示例中,重复的代码可以重构出来并放入一个单独的函数中,但有时这种重构不可用或不容易实现

#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())
where
Func1
void Func1(t arg)
无论如何都无法工作:),但是根据这个规则,我们不必对函数进行两次重载

解决方案#1:完美转发

不幸的是,没有这样一个简单的规则可以使同一个函数不必实现两个重载:一个采用常量左值引用,另一个采用右值引用。相反,完美的转发被设计出来了

template <typename U>
void Func(U &&param) // 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
}