4

Следуя What is the copy and swap idiom и How 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. Я застрял с подходом друзей, не являющимся членами?

EDIT: перейдите по ссылке my answer below, чтобы узнать, что у меня получилось, благодаря всем ответам и комментариям по этому вопросу.

+0

никоим образом не ваш код следовать [успешное решение] (http://stackoverflow.com/a/3279550/366904) в GMan-х ответ. –

+1

Если вы правильно реализуете оператор nothrow move-assign и move-constructor, нет необходимости реализовывать swap. –

+1

Внутри класса, почему бы просто не вызвать 'this-> swap (other)'? – Yakk

ответ

5

Я принимаю это мы пост C++ 11?

В этом случае, реализация по умолчанию станд :: свопа будет оптимальным, если мы правильно реализовать оператор присваивания движение и перемещение-конструктор (в идеале) 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 

EDIT:

В интересах любого, кто обеспокоен производительностью (например, @JosephThompson) позвольте мне смягчить ваши проблемы. После переезда вызов std::swap в виртуальную функцию (чтобы заставить лязг производить любой код на всех), а затем компиляции с яблоком лязгом с -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++!

+0

Да для C++ 11 (я использую gcc -std = C++ 14). На самом деле, я был бы вполне доволен оператором по умолчанию =, но для дополнительного принуждения принуждения, которое я хочу во внутреннем состоянии класса. Конечно, я согласен с вашими комментариями о необходимости (или нет) соблюдения() в некоторых специальных функциях. Особенно в контексте этого примера, где бутылки будут проходить через крышу довольно быстро! –

+0

Однако, если Bar унаследован от Foo, и если бы у меня было что-то вроде этого 'Bar (Foo const &)' (скажите, если это плохой дизайн), возможно, я хотел бы убедиться, что принудительное() все еще называется eg. 'Bar (Foo const & f): Foo (f) {enforce(); } ' –

+0

уверен, все в порядке. Просто, если вы строите из бара, то для того, чтобы существовать, этот Bar * должен * быть действительным, поскольку он будет проходить свою собственную проверку соблюдения. поэтому вызов «принудительного исполнения» в конструкторе копирования не нужен (при условии, что принудительное выполнение бросков при сбое, как и должно быть) –

4

Примечание: Это pre C++ 11 способ использования копирования и свопинга. Для решения C++ 11 см. this answer

Для того, чтобы это сработало, вам нужно исправить пару вещей. Сначала вам нужно отправить декларацию бесплатной функции обмена, чтобы 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; 
} 

Ставит все это вместе, и мы получаем этот Live Example

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

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

... хотя этот ответ закрывает стабильную дверь после того, как лошадь заперта болтами, не так ли? почему бы просто не использовать функцию замены членов внутри функций-членов? –

+1

@RichardHodges Кажется, что OP не хочет иметь функцию-член или функцию друга. Похоже, он использует вариант 2 из [this] (http://stackoverflow.com/a/6380882/4342498) ответа. Я не знаю, какой путь на самом деле лучше, или если вы можете сказать, что один способ лучше, чем другой, поэтому я представил это, чтобы заставить его работать. – NathanOliver

+0

@NathanOliver Извините, я имел в виду бутылки * = 2, это немного надуманное здесь, но принудительное() будет уменьшать() в классе фракций, поддерживаемом в сокращенной форме. –

0

В этой функции также необходимо включить std::swap.

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

Цитируя answer you referred к:

Если своп в настоящее время используется, как показано в 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

1

Вы знаете, что Bar имеет функцию в swap члена, поэтому назвать его непосредственно.

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

-нечленов swap существует только для того, чтобы клиенты Bar могут воспользоваться оптимизированной swap реализации, не зная, существует ли она, используя using std::swap идиомы, чтобы включить argument-dependent lookup:

using std::swap; 
swap(a, b); 
+0

Вы имеете в виду, что внутри Bar, я должен просто использовать функцию 'swap' member, а для клиентов Bar, я должен предоставить не-член' swap'? –

+2

Да. Вызов не-членов 'swap' из вашего класса кажется немного бессмысленным, так как он в конечном итоге вызывает« swap »члена. Не-членский своп - это просто поддержка '' использования std :: swap''' для включения ADL. Это особенно полезно в общем коде, где вы меняете объекты неизвестного типа. –

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

 Смежные вопросы

  • Нет связанных вопросов^_^