Следующий Что такое копия и идиома а также Как предоставить функцию подкачки для моего класса, Я попытался реализовать функцию подкачки, как в последнем принятом варианте ответа № 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. Я застрял с подходом функции, не являющейся членом-другом?
РЕДАКТИРОВАТЬ: Перейти к мой ответ ниже чтобы увидеть, чем я закончил, спасибо всем ответам и комментариям на этот вопрос.
Я так понимаю, мы публикуем 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
Увидеть? оптимальный. Стандартная библиотека С ++ рушится!
Примечание: это предварительный 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
}
Ты знаешь что 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);
Вам нужно включить 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 ...
}
Для приведенного выше контекста, где нужно только применить какое-то внутреннее ограничение, лучше использовать значение по умолчанию и просто применять только один раз ограничение в конструкторе прямой инициализации. Тем не менее, если вам нужно реализовать эти функции, посмотрите на ответ @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;
}