Kiedy przeciążenie pass by reference (wartość l i wartość r) jest preferowane od pass-by-value?

Widziałem, że mówi się, że operator= napisane, aby wziąć parametr tego samego typu by-value służy zarówno jako operator przypisania kopiowania i operator przypisania ruchu w C++11:

Foo& operator=(Foo f)
{
    swap(f);
    return *this;
}

Gdzie alternatywą byłoby więcej niż dwa razy więcej linii z dużą ilością powtórzeń kodu i potencjałem błędu:

Foo& operator=(const Foo& f)
{
    Foo f2(f);
    swap(f2);
    return *this;
}

Foo& operator=(Foo&& f)
{
    Foo f2(std::move(f));
    swap(f2);
    return *this;
}

W jakich okolicznościach jest lepsze przeciążenie ref-to-const i R-value niż pass by value, czy kiedy jest to konieczne? Myślę o std::vector::push_back, na przykład, który jest zdefiniowane jako dwa przeciążenia:

void push_back (const value_type& val);
void push_back (value_type&& val);

Po pierwszym przykładzie, gdzie wartość pass by służy jako przypisanie kopii operator i operator przypisania ruchu, nie może push_back być zdefiniowany w Standard, aby być pojedynczą funkcją?

void push_back (value_type val);
Author: M.M, 2013-08-19

1 answers

Dla typów, których operator kopiowania może przetwarzać zasoby, zamiana z kopią prawie nigdy nie jest najlepszym sposobem implementacji operatora kopiowania. Na przykład spójrz na std::vector:

Ta klasa zarządza buforem o dynamicznym rozmiarze i utrzymuje zarówno capacity (maksymalną długość bufora), jak i size (bieżącą długość). Jeśli vector operator przypisania kopii jest zaimplementowany swap, to bez względu na wszystko, nowy bufor jest zawsze alokowany, jeśli rhs.size() != 0.

Jednakże, jeśli lhs.capacity() >= rhs.size(), nie ma potrzeby przydzielania nowego bufora. Można po prostu przypisać/skonstruować elementy z rhs do lhs. Gdy Typ elementu jest trywialnie kopiowalny, może to sprowadzać się do niczego innego jak memcpy. Może to być znacznie, [38]}znacznie szybciej niż przydzielanie i dealokacja bufora.

Ten sam problem dla std::string.

Ten sam problem dla MyType gdy MyType ma elementy danych, które są std::vector i/lub std::string.

There are only 2 times you want aby rozważyć implementację przypisania kopii za pomocą swapu:

  1. Wiesz, że metoda swap (w tym obowiązkowa konstrukcja kopii, gdy rhs jest lvalue) nie będzie strasznie nieefektywna.

  2. Wiesz, że będziesz zawsze potrzebował operatora przypisywania kopii, aby mieć silną gwarancję bezpieczeństwa WYJĄTKÓW.

Jeśli nie jesteś pewien 2, innymi słowy myślisz, że operator przypisania kopii może czasami potrzebujesz silnej gwarancji bezpieczeństwa WYJĄTKÓW, nie wdrażaj przypisania w kategoriach swap. To jest łatwe dla Twoich klientów, aby uzyskać tę samą gwarancję, Jeśli podasz jeden z:

  1. noexcept swap.
  2. noexcept operator przypisania ruchu.

Na przykład:

template <class T>
T&
strong_assign(T& x, T y)
{
    using std::swap;
    swap(x, y);
    return x;
}

Lub:

template <class T>
T&
strong_assign(T& x, T y)
{
    x = std::move(y);
    return x;
}

Teraz będą pewne typy, w których implementacja przypisania kopii za pomocą swap będzie miała sens. Jednak te typy będą wyjątkiem, a nie zasada.

On:

void push_back(const value_type& val);
void push_back(value_type&& val);

Imagine vector<big_legacy_type> gdzie:

class big_legacy_type
{
 public:
      big_legacy_type(const big_legacy_type&);  // expensive
      // no move members ...
};

Gdybyśmy mieli tylko:

void push_back(value_type val);

Wtedy push_backing an lvalue big_legacy_type w vector wymagałoby 2 Kopii zamiast 1, nawet jeśli capacity było wystarczające. To byłaby katastrofa, biorąc pod uwagę wydajność.

Update

[33]}Oto HelloWorld, który powinien być w stanie uruchomić na dowolnej platformie zgodnej z C++11:]}
#include <vector>
#include <random>
#include <chrono>
#include <iostream>

class X
{
    std::vector<int> v_;
public:
    explicit X(unsigned s) : v_(s) {}

#if SLOW_DOWN
    X(const X&) = default;
    X(X&&) = default;
    X& operator=(X x)
    {
        v_.swap(x.v_);
        return *this;
    }
#endif
};

std::mt19937_64 eng;
std::uniform_int_distribution<unsigned> size(0, 1000);

std::chrono::high_resolution_clock::duration
test(X& x, const X& y)
{
    auto t0 = std::chrono::high_resolution_clock::now();
    x = y;
    auto t1 = std::chrono::high_resolution_clock::now();
    return t1-t0;
}

int
main()
{
    const int N = 1000000;
    typedef std::chrono::duration<double, std::nano> nano;
    nano ns(0);
    for (int i = 0; i < N; ++i)
    {
        X x1(size(eng));
        X x2(size(eng));
        ns += test(x1, x2);
    }
    ns /= N;
    std::cout << ns.count() << "ns\n";
}

Zakodowałem X's copy assignment operator two sposoby:

  1. implicite, co jest równoważne wywołaniu vector's copy assignment operator.
  2. Z idiomem copy/swap, sugestywnie pod makrem SLOW_DOWN. Myślałem o nazwaniu go SLEEP_FOR_AWHILE, ale ten sposób jest znacznie gorszy niż Oświadczenia snu, jeśli jesteś na urządzeniu zasilanym bateryjnie.

Test konstruuje niektóre losowo wielkości vector<int> s od 0 do 1000 i przypisuje je milion razy. Razy każdy z nich, sumuje czasy, a następnie znajduje średnią czas w nanosekundach zmiennoprzecinkowych i wypisuje to. Jeśli dwa kolejne wywołania zegara wysokiej rozdzielczości nie zwrócą czegoś mniej niż 100 nanosekund, możesz zwiększyć długość wektorów.

Oto moje wyniki:

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp
$ a.out
428.348ns
$ a.out
438.5ns
$ a.out
431.465ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DSLOW_DOWN test.cpp
$ a.out
617.045ns
$ a.out
616.964ns
$ a.out
618.808ns

Widzę 43% skuteczność idiomu copy / swap z tym prostym testem. YMMV.

[33]}powyższy test średnio ma wystarczającą pojemność na lhs połowę czasu. Jeśli weźmiemy to do extreme:
    Lhs ma wystarczającą pojemność przez cały czas. Lhs nie ma wystarczającej pojemności.

Wtedy przewaga wydajności domyślnego przypisania kopii nad idiomem copy / swap waha się od około 560% do 0%. Idiom copy/swap nigdy nie jest szybszy i może być znacznie wolniejszy (w tym teście).

Chcesz Prędkości? Miara.

 26
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-19 00:59:51