Реализация своп в копии и идиома своп

Следующий Что такое копия и идиома а также Как предоставить функцию подкачки для моего класса, Я попытался реализовать функцию подкачки, как в последнем принятом варианте ответа № 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

Решение

Я так понимаю, мы публикуем 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 вообще генерировать любой код), а затем компилировать с помощью apple clang с -O2, это:

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

Увидеть? оптимальный. Стандартная библиотека С ++ рушится!

5

Другие решения

Примечание: это предварительный C ++ 11 способ использования копирования и обмена. Для решения C ++ 11 см. этот ответ

Чтобы заставить это работать, вам нужно исправить пару вещей. Для начала необходимо объявить функцию swap free, чтобы operator= знает об этом. Для этого вам также необходимо отправить декларацию Bar так swap что есть тип с именем бар

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;
}

Мы собрали все это вместе и получили это Живой пример

Действительно, хотя копия operator = должен выглядеть так

Bar & operator=(Bar b) // makes copy
{
::swap(*this, b) // swap the copy
return *this; // return the new value
}
4

Ты знаешь что 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);
1

Вам нужно включить 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 ...
}

Жить на Колиру

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;
}
0