4

What is the copy and swap idiomHow to provide a swap function for my class之后,我尝试像在后者接受的答案选项 2 (具有调用成员函数的自由函数)中那样实现交换函数,而不是直接友好前一个链接中的免费功能。

但是以下内容无法编译

#include <iostream>

// Uncommenting the following two lines won't change the state of affairs
// class Bar;
// void swap(Bar &, Bar &);
class Bar {
public:
  Bar(unsigned int bottles=0) : bottles(bottles) { enforce(); } // (1)
  Bar(Bar const & b) : bottles(b.bottles) { enforce(); } // (1)

  Bar & operator=(Bar const & b) {
    // bottles = b.bottles;
    // enforce();
    // Copy and swap idiom (maybe overkill in this example)
    Bar tmp(b); // but apart from resource management it allows (1)
                // to enforce a constraint on the internal state
    swap(*this, tmp); // Can't see the swap non-member function (2)
    return *this;
  }

  void swap(Bar & that) {
    using std::swap;
    swap(bottles, that.bottles);
  }

  friend std::ostream & operator<<(std::ostream & out, Bar const & b) {
    out << b.bottles << " bottles";
    return out;
  }

private:
  unsigned int bottles;
  void enforce() { bottles /=2; bottles *= 2; } // (1) -- Ensure the number of bottles is even
};

void swap(Bar & man, Bar & woman) { // (2)
  man.swap(woman);
}

int main () {
  Bar man (5);
  Bar woman;

  std::cout << "Before -> m: " << man << " / w: " << woman << std::endl;
  swap(man, woman);
  std::cout << "After  -> m: " << man << " / w: " << woman << std::endl;

  return 0;
}

我知道复制和交换习语在这里有点过分,但它也允许人们通过复制构造函数 (1) 对内部状态实施一些约束(一个更具体的例子是保持一个简化形式的分数)。不幸的是,这不能编译,因为编译器看到的 (2) 的唯一候选者是 Bar::swap 成员函数。我是否坚持使用朋友非成员函数方法?

编辑:转到我下面的答案,看看我最终得到了什么,这要感谢关于这个问题的所有答案和评论。

4

5 回答 5

5

我认为我们发布了 c++11?

在这种情况下,std::swap 的默认实现将是最佳的,前提是我们正确实现了移动赋值运算符和移动构造函数(理想情况下是 nothrow)

http://en.cppreference.com/w/cpp/algorithm/swap

#include <iostream>

class Bar {
public:
    Bar(unsigned int bottles=0) : bottles(bottles) { enforce(); } // (1)
    Bar(Bar const & b) : bottles(b.bottles) {
        // b has already been enforced. is enforce necessary here?
        enforce();
    } // (1)
    Bar(Bar&& b) noexcept
    : bottles(std::move(b.bottles))
    {
        // no need to enforce() because b will have already been enforced;
    }

    Bar& operator=(Bar&& b) noexcept
    {
        auto tmp = std::move(b);
        swap(tmp);
        return *this;
    }

    Bar & operator=(Bar const & b)
    {
        Bar tmp(b); // but apart from resource management it allows (1)
        swap(tmp);
        return *this;
    }

    void swap(Bar & that) noexcept {
        using std::swap;
        swap(bottles, that.bottles);
    }

    friend std::ostream & operator<<(std::ostream & out, Bar const & b) {
        out << b.bottles << " bottles";
        return out;
    }

private:
    unsigned int bottles;
    void enforce() {  } // (1)
};

/* not needed anymore
void swap(Bar & man, Bar & woman) { // (2)
    man.swap(woman);
}
*/
int main () {
    Bar man (5);
    Bar woman;

    std::cout << "Before -> m: " << man << " / w: " << woman << std::endl;
    using std::swap;
    swap(man, woman);
    std::cout << "After  -> m: " << man << " / w: " << woman << std::endl;

    return 0;
}

预期结果:

Before -> m: 5 bottles / w: 0 bottles
After  -> m: 0 bottles / w: 5 bottles

编辑:

为了任何关心性能的人(例如@JosephThompson)的利益,请允许我减轻您的担忧。在将调用移动std::swap到一个虚拟函数中(以强制 clang 生成任何代码),然后使用带有 -O2 的 apple clang 进行编译,这:

void doit(Bar& l, Bar& r) override {
    std::swap(l, r);
}

变成了这样:

__ZN8swapper24doitER3BarS1_:            ## @_ZN8swapper24doitER3BarS1_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp85:
    .cfi_def_cfa_offset 16
Ltmp86:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp87:
    .cfi_def_cfa_register %rbp
    movl    (%rsi), %eax
    movl    (%rdx), %ecx
    movl    %ecx, (%rsi)
    movl    %eax, (%rdx)
    popq    %rbp
    retq
    .cfi_endproc 

看?最佳。c++ 标准库摇滚!

于 2016-03-15T12:32:50.677 回答
4

注意:这是使用复制和交换的 C++11 之前的方式。对于 C++11 解决方案,请参阅此答案

为了让它工作,你需要解决一些问题。首先,您需要转发声明交换免费功能,以便operator=了解它。为此,您还需要转发声明Bar,以便swap有一个名为 bar 的类型

class Bar;

void swap(Bar & man, Bar & woman);

// rest of code

然后我们需要告诉编译器在哪里寻找swap. 我们这样做的方法是使用范围解析运算符。这将告诉编译器在类的外部范围内查找swap函数

Bar & operator=(Bar const & b) {
  // bottles = b.bottles;
  // enforce();
  // Copy and swap idiom (maybe overkill in this example)
  Bar tmp(b); // but apart from resource management it allows (1)
            // to enforce a constraint on the internal state
  ::swap(*this, tmp); // Can't see the swap non-member function (2)
//^^ scope operator 
  return *this;
}

我们把所有这些放在一起,我们得到了这个Live Example

真的,虽然副本operator =应该看起来像

Bar & operator=(Bar b) // makes copy
{
    ::swap(*this, b) // swap the copy
    return *this; // return the new value
}
于 2016-03-15T12:11:56.547 回答
1

你知道它Bar有一个swap成员函数,所以直接调用它。

Bar& operator=(Bar const& b) {
    Bar tmp(b);
    tmp.swap(*this);
    return *this;
}

非成员swap仅存在,以便客户端Bar可以利用其优化swap实现而不知道它是否存在,使用using std::swap成语启用参数相关查找

using std::swap;
swap(a, b);
于 2016-03-15T12:31:08.800 回答
0

对于上述上下文,只需要强制执行一些内部约束,最好使用默认值,并且只在直接初始化构造函数中强制执行一次约束。不过,如果您需要实现这些功能,请查看@RichardHodges 的答案!另请参阅@HowardHinnant 评论(特别是关于当编译器执行魔术时隐式声明特殊成员的幻灯片部分......)。

这是我最终得到的结果(不再有明确的复制和交换):

#include <iostream>

class Bar {
public:
  Bar(unsigned int bottles=0) : bottles(bottles) { enforce(); } // The only point of enforcement

  friend std::ostream & operator<<(std::ostream & out, Bar const & b) {
    out << b.bottles << " bottles";
    return out;
  }

private:
  unsigned int bottles;
  void enforce() { bottles /= 2; bottles *=2; }
};

int main () {
  Bar man (5);
  Bar woman;

  std::cout << "Before -> m: " << man << " / w: " << woman << std::endl;
  using std::swap; // Argument dependent lookup
  swap(man, woman);
  std::cout << "After  -> m: " << man << " / w: " << woman << std::endl;

  return 0;
}

现在,如果 Bar 继承自 Foo 会发生什么(不需要enforce)。这是最初的用例,让我觉得我需要展开我自己的特殊功能,并从复制的复制部分中获利,并将惯用语交换到enforce约束中。事实证明,即使在这种情况下,我也不需要:

#include <iostream>

class Foo {
public:
  Foo(unsigned int bottles=11) : bottles(bottles) {} // This is odd on purpose

  virtual void display(std::ostream & out) const {
    out << bottles << " bottles";
  }

protected:
  unsigned int bottles;
};

std::ostream & operator<<(std::ostream & out, Foo const & f) {
  f.display(out);
  return out;
}

class Bar : public Foo {
public:
  Bar(unsigned int bottles=0) : Foo(bottles) { enforce(); }
  Bar(Foo const & f) : Foo(f) { enforce(); }

  void display(std::ostream & out) const override {
    out << bottles << " manageable bottles";
  }

private:
  void enforce() { bottles /= 2; bottles *=2; }
};

int main () {
  Bar man (5); // Again odd on purpose
  Bar woman;

  std::cout << "Before -> m: " << man << " / w: " << woman << std::endl;
  using std::swap; // Argument dependent lookup
  swap(man, woman);
  std::cout << "After  -> m: " << man << " / w: " << woman << std::endl;

  Foo fool(7); // Again odd
  Bar like(fool);
  std::cout << fool << " -> (copy) " << like << std::endl;
  Bar crazy;
  crazy = fool;
  std::cout << fool << " ->   (=)  " << crazy << std::endl;

  return 0;
}
于 2016-03-16T07:48:46.190 回答
0

您还需要启用std::swap该功能。

using std::swap;
swap(*this, tmp); // Can't see the swap non-member function (2)

引用您提到的答案

如果现在如 1) 所示使用交换,您的函数将被找到。

它的使用方式:

{
  using std::swap; // enable 'std::swap' to be found
                   // if no other 'swap' is found through ADL
  // some code ...
  swap(lhs, rhs); // unqualified call, uses ADL and finds a fitting 'swap'
                  // or falls back on 'std::swap'
  // more code ...
}

Live on Coliru

于 2016-03-15T12:21:16.453 回答