2017-07-12 12 views
7

daję następujące przykłady ilustrujące moje pytanie:Jak zmniejszyć zużycie pamięci operatora + dla własnej klasy w C++?

class BigClass 
{ 
public: 
    static int destruct_num; 
    friend BigClass operator + (const BigClass &obj1, const BigClass &obj2); 
    std::vector<int> abc; 

    BigClass() 
    { 

    } 

    ~BigClass() 
    { 
     destruct_num++; 
     std::cout << "BigClass destructor " <<destruct_num<< std::endl; 

    } 

    BigClass(BigClass&& bc) :abc(std::move(bc.abc)) 
    { 
     std::cout << "move operation is involed" << std::endl; 
    } 
}; 

int BigClass::destruct_num = 0; 

BigClass operator + (const BigClass &obj1, const BigClass &obj2) 
{ 
    BigClass temp; 
    temp.abc = obj1.abc; 
    temp.abc.insert(temp.abc.end(), obj2.abc.begin(), obj2.abc.end()); 

    return temp; 

} 

int main(void) 
{ 

    BigClass a; 
    a.abc = { 1,2,3 }; 
    BigClass b; 
    b.abc = { 5,6,7 }; 
    BigClass c = a + b; 

// for (auto& v : c.abc) 
//  std::cout << v << " "; 

    return 0; 


} 

Jeden problem w odniesieniu do operator + jest to, że mamy do wygenerowania temp BigClass obiektu czasowo, a potem zwrócić go. Czy są jakieś sposoby na zmniejszenie tego obciążenia?

+0

Możliwy duplikat: https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading – Rakete1111

+2

kopia elizja? http://en.cppreference.com/w/cpp/language/copy_elision – cppBeginner

+4

można uniknąć tymczasowego, ale jedynym sposobem uniknięcia tworzenia nowej instancji nie jest użycie 'operator ='. 'operator + =' nie wymaga tworzenia nowej instancji – user463035818

Odpowiedz

7

Ogólnie:

[...] kompilatory są dozwolone, ale nie jest wymagane, aby pominąć kopię [...]

Zresztą czynność powinna być zoptymalizowana przez każdego nowoczesnego kompilatora ponieważ copy elision.

Here przykład:

Wynik operator+ jest w sekcji montażowej:

call operator+(BigClass const&, BigClass const&) 
addl $12, %esp 

Jak widać, żaden konstruktor kopia jest wywoływana w celu skopiowania wynik.

Rzeczywiście, jeśli wyłączyć optymalizację kopia elizja w GCC Z result Zmiany:

call operator+(BigClass const&, BigClass const&) 
addl $12, %esp 
subl $8, %esp 
leal -20(%ebp), %eax 
pushl %eax 
leal -56(%ebp), %eax 
pushl %eax 
call BigClass::BigClass(BigClass&&) 
addl $16, %esp 
subl $12, %esp 
leal -20(%ebp), %eax 
pushl %eax 
call BigClass::~BigClass() 
addl $16, %esp 

Po wezwaniu operator+ kopia (lub poruszać się w tym przypadku) konstruktor nazywa, i po destruktora tymczasowego obiektu.

Należy pamiętać, że kopiowanie jest uzyskiwane nawet wyłączając optymalizacje (-O0).

Ten sam wynik uzyskano dla starszej wersji: GCC 4.4.7.


Od kopia elizja nie jest gwarantowana dla wszystkich architektur, można wdrożyć kilka różnych rozwiązań.

Jednym z możliwych rozwiązań jest uniknięcie przypisania zmiennej tymczasowej do funkcji, wymagającej od osoby dzwoniącej rezerwacji tej przestrzeni. Aby to zrobić, należy użyć metody "niestandardowej" i unikać przeciążania operator+.

void sum_bigClasses(const BigClass& obj1, const BigClass& obj2, BigClass& output) { 
    // output.resize(obj1.size() + obj2.size()); 
    // std::copy(...); 
} 

Innym rozwiązaniem może być realizacji operator const do sumy.Przykład:

BigClass& operator+=(const BigClass& rhs) { 
    // std::copy(rhs.cbegin(), rsh.cend(), std::back_inserter(abc)); 
    return *this; 
} 

W ten sposób interfejs klasa umożliwia różne strategie:

  • Unikać przeznaczyć 3 inny obiekt, ale tylko 2, jeśli nie trzeba zachować wszystkie różne stany.
  • Umożliwia przydzielenie 3 różnych obiektów i uniknięcie tymczasowej konstrukcji wewnątrz operatora.

Oto dwa przykłady.

Pierwszy punkt

BigClass o1; 
BigClass o2; 
// Fill o1 and o2; 
o1 += o2; 
// only 2 object are alive 

Drugi punkt

BigClass o1; 
BigClass o2; 
// Fill o1 and o2; 
BigClass o3 = o1; // Costructor from o1 
o3 += o2; 
// three different object 

EDIT: Ponieważ funkcja to NRVO (zwrócona ekspresja nie jest prvalue) ani nowy standard C++ 17 w chory gwarantuje kopiowanie.

+0

Czy C++ 17 nie gwarantuje kopiowania tylko dla RVO? Ciało operatora '' + '' wywoływałoby NRVO. –

+1

@underscore_d Masz całkowitą rację! Zaktualizuję moją odpowiedź, dziękuję! –

1

Jeśli uruchomisz swój kod, zobaczysz tylko 3 destruktory. Oznacza to, że wartość obiektu tmp jest przenoszona, a nie kopiowana z powodu RVO (Optymalizacja wartości zwracanej). Kompilator nie kopiuje go, ponieważ widzi, że nie jest to konieczne.

0

Użycie tymczasowych nie tylko powoduje marnowanie pamięci, ale także czas przetwarzania (obliczanie sumy instancji N BigClass może mieć złożoność czasu kwadratowego w N). Nie ma ogólnego rozwiązania, aby tego uniknąć, ponieważ zależy to od sposobu użycia twoich obiektów. W tym scenariuszu:

BigClass c = a + b; 

kompilator jest już wolny (lub wymagane, C++ 17), aby użyć kopii elizja, jak wyjaśnił banana36, a wejścia są lwartościami, dlatego nie mogą być zmieniane bez potencjalnie powodując wielki niespodzianka.

Odmienny scenariusz byłby:

BigClass f(); 
BigClass g(); 

BigClass h = f() + g(); 

w tym przypadku f() i g() są rvalues ​​i kopiowanie obu z nich jest marnotrawstwem. Przechowywanie co najmniej jednego z nich może być ponownie użyte, np. można napisać dodatkowy operator + przeciążenie celu optymalizacji przypadek, w którym w lewo do składnika jest rvalue:

BigClass operator +(BigClass &&a, const BigClass &b) 
{ 
    a.abc.insert(a.abc.end(), b.abc.begin(), b.abc.end()); 
    return std::move(a); 
} 

To ponowne wykorzystanie pamięci masowej a.abc „s i zapobiega kopiowaniu jej zawartość, o ile pojemność jest wystarczająca. Miłym efektem ubocznym jest np. sumując N obiektów z 10 elementami, każdy będzie miał liniową wydajność, ponieważ wstawienie stałej liczby elementów na końcu std::vector ma stały zamortyzowany koszt. Działa to jednak tylko wtedy, gdy wybrane jest właściwe przeciążenie operator +, np. to nie sprawa dla std::accumulate. Oto przegląd głównych opcji:

  1. Supply operator +(const BigClass &, const BigClass &) i operator += i edukowania użytkowników na temat implikacji Wyniki wykorzystania były niedbale.
  2. Prawdopodobnie dodaj przeciążenia dla operator +(BigClass &&, const BigClass &) i może operator +(const BigClass &, BigClass &&) i operator +(BigClass &&, BigClass &&).Zauważ, że jeśli masz oba przeciążenia z jednym odwołaniem rvalue, absolutnie musisz dodać przeciążenie z dwoma odwołaniami rvalue, inaczej f() + g() będzie niejednoznacznym wywołaniem. Zauważ również, że przeciążenie, w którym parametr prawej ręki jest wartością rvalue, najlepiej nadaje się do użycia z np. std::deque, a nie std::vector, ponieważ ma mniejszą złożoność czasu na wstawianiu z przodu, ale zastąpienie wektora blokadą jest przydatne tylko wtedy, gdy ten przypadek użycia jest powszechny, ponieważ deque jest inaczej wolniejszy niż wektor.
  3. Udostępniaj tylko wydajne operacje, takie jak operator += i radzić sobie z frustracją użytkowników (alternatywnie, podaj mniej wydajne operacje, które dyskredytują takie nazwy, jak copyAdd).
Powiązane problemy