Bir surət və dəyişdirmə deyim nədir?

Bu deyim nədir və nə vaxt istifadə olunmalıdır? Hansı problemləri həll edir? C ++ 11 ilə deyim dəyişirmi?

Bir çox yerlərdə qeyd olunmasına baxmayaraq, bizdə "bu nədir" suallar və cavablar yoxdur, buna görə də burada. Burada əvvəlcədən qeyd olunan yerlərin qismən siyahısı:

1717
19 июля '10 в 11:42 2010-07-19 11:42 GManNickG 19 iyul 'da saat 11: 42-də təyin olunub 2010-07-19 11:42
@ 5 cavab

Baxın

Niyə kopyalayıcı və əvəzedici deyimə ehtiyacımız var?

Bir qaynağı idarə edən hər hansı bir sinif (ağıllı bir pointer kimi bir qabıq) Üç Üçü tətbiq etməlidir. Kopyalama konstruktorunun və dağıdıcıın məqsədi və tətbiqi sadədirsə də, surətləndirmə operatoru bəlkə də ən çox nüvə və kompleksdir. Bunu necə edirsiniz? Qorunmaq üçün hansı tələlər var?

Kopyala və dəyişdirmə deyim bir həlldir və şəffaf bir şəkildə iki şeyə nail olmaq üçün tapşırıq operatoruna kömək edir: kod çoğaltmasının qarşısını almaq və etibarlı istisna təminatı təmin etmək .

Necə işləyir?

Konseptual olaraq , məlumatın yerli bir kopyasını yaratmaq üçün surəti-konstruktor funksiyasından istifadə edərək, köhnə məlumatları yeni məlumatlarla əvəz etmək, köçürmə məlumatlarını swap funksiyasından istifadə edir. Daha sonra köhnə məlumatları alaraq müvəqqəti surət məhv edilir. Yeni məlumatların bir surətini buraxırıq.

Kopyalama və dəyişdirmə deyimlərini istifadə etmək üçün üç şeyə ehtiyacımız var: işləyən nümunə qurucusu, işləyən yıxıcı (hər ikisi də hər hansı bir qabığın əsasıdır, beləliklə bitirilməlidir) və swap funksiyası.

Swap funksiyası, iki dərəcəli obyektlərin, bir üzv üçün üzv olan swap funksiyası deyil. std::swap təmin etmə əvəzinə std::swap istifadə etmək cazibədar ola bilər, lakin bu mümkün deyil; std::swap bir konstruktor nümunəsi və onun yerinə yetirilməsində bir surəti tapşırıq operatoru istifadə edir və nəticədə biz özümüz baxımından təyinat operatorunu müəyyən etməyə çalışacağıq!

(Yalnız bu deyil, eyni zamanda qeyri-dəqiqləşdirilmiş swap zəngləri bizim xüsusi dəyişdirmə operatoru istifadə edəcək, lazımsız konstruksiyaların atılmasına və std::swap gətirib çıxaracaq olan sinfi məhv edəcəyik.)


Ətraflı izahat

Məqsədi

Xüsusi bir vəziyyətə baxın. Biz lazımsız sinfi dinamik bir sıra ilə idarə etmək istəyirik. İş konstruktoru, surəti qurucusu və dağıdıcı ilə başlayaq:

 #include <algorithm> // std::copy #include <cstddef> // std::size_t class dumb_array { public: // (default) constructor dumb_array(std::size_t size = 0) : mSize(size), mArray(mSize ? new int[mSize]() : nullptr) { } // copy-constructor dumb_array(const dumb_array other) : mSize(other.mSize), mArray(mSize ? new int[mSize] : nullptr), { // note that this is non-throwing, because of the data // types being used; more attention to detail with regards // to exceptions must be given in a more general case, however std::copy(other.mArray, other.mArray + mSize, mArray); } // destructor ~dumb_array() { delete [] mArray; } private: std::size_t mSize; int* mArray; }; 

Bu sinif demək olar ki, çox uğurla idarə edir, lakin düzgün əməliyyat üçün operator= tələb edir.

Səhv qərar

Burada sadəlövh bir tətbiq necə görünə bilər?

 // the hard part dumb_array operator=(const dumb_array other) { if (this !=  // (1) { // get rid of the old data... delete [] mArray; // (2) mArray = nullptr; // (2) *(see footnote for rationale) // ...and put in the new mSize = other.mSize; // (3) mArray = mSize ? new int[mSize] : nullptr; // (3) std::copy(other.mArray, other.mArray + mSize, mArray); // (3) } return *this; } 

Və biz edəcəyik deyirik; indi sızan olmadan diziyi nəzarət edir. Lakin, kodda (n) olaraq sıralanan üç problemdən əziyyət çəkir.

  • Birincisi homing testidir. Bu çek iki məqsədə xidmət edir: öz-özünə təyin edilməsi üçün lazımsız kodu yayınmamağımızın qarşısını almaq və bizi incə səhvlərdən qoruyan (məsələn, yalnız bir nüsxəni surət və surətdə çıxarmaq üçün) silməkdir. Amma digər bütün hallarda, sadəcə proqramı yavaşlatır və kodda səs kimi çıxış edir; özünü araşdırma nadir hallarda baş verir, buna görə də bu çeklərin çoxu sərf olunur. Operator normal olmadan işləyə bilsəydi daha yaxşı olardı.

  • İkincisi, yalnız əsas istisna təminat təmin edir. new int[mSize] , *this dəyişəcək. (Yəni, ölçüsü səhvdir və məlumatlar getdi!) Təhlükəsiz istisna təminat üçün, bu bir şey olmalıdır:

     dumb_array operator=(const dumb_array other) { if (this !=  // (1) { // get the new data ready before we replace the old std::size_t newSize = other.mSize; int* newArray = newSize ? new int[newSize]() : nullptr; // (3) std::copy(other.mArray, other.mArray + newSize, newArray); // (3) // replace the old data (all are non-throwing) delete [] mArray; mSize = newSize; mArray = newArray; } return *this; } 
  • Kod genişlənmişdir! Bu, üçüncü problemə gətirib çıxarır: kodu çoğaltma. Bizim hədəf operatorumuz digər yerlərdə yazdığımız bütün kodları effektiv şəkildə dublikatlandırır və bu, dəhşətli bir şeydir.

Bizim vəziyyətimizdə, əsas yalnız iki xəttdən ibarətdir (seçim və surət), lakin daha çətin resurslarla bu şişirdilmiş kod olduqca mürəkkəb ola bilər. Özümüzü təkrarlamamağa çalışmalıyıq.

(Düşünürsünüz: bu kod bir qaydanı düzgün idarə etmək üçün lazım olsa, əgər mənim sinif birdən çox idarə edirsə, nə olarsa? Həqiqi problem kimi görünsə də və əslində qeyri-trivial try / catch tələb edirsə, bu problem deyil. yalnız bir resurs idarə etməlidir!)

Müvəffəqiyyətli qərar

Artıq qeyd edildiyi kimi, surət və dəyişdirmə deyim bütün bu problemləri həll edəcəkdir. Ancaq indi bir başqa istisna var: bir swap . Üç üstünlüyün müvəffəqiyyətli olması bizim surəti konstruktorumuz, tapşırıq operatoru və dağıdıcı olmağımıza səbəb olsa da, həqiqətən, "Böyük üç və bir yarım" adlandırılmalıdır: hər hansı bir zamanda sinfinizin bir qaynağı idarə edəcəyi, bu da bir swap təmin etmək məcburiyyətindədir.

Sınıfımıza dəyişdirmə funksiyası əlavə etməliyik və bunu aşağıdakı kimi edəcəyik:

 class dumb_array { public: // ... friend void swap(dumb_array first, dumb_array second) // nothrow { // enable ADL (not necessary in our case, but good practice) using std::swap; // by swapping the members of two objects, // the two objects are effectively swapped swap(first.mSize, second.mSize); swap(first.mArray, second.mArray); } // ... }; 

( Bu , public friend swap izah edir). İndi biz yalnız dumb_array mübadiləsi edə dumb_array , lakin svoplar ümumiyyətlə daha səmərəli ola bilər; bütün serialları ayırmaqdan və kopyalamadan daha çox göstəricilər və ölçüləri dəyişir. Funksiyalar və səmərəliliyin bu bonusuna əlavə olaraq, biz artıq nüsxə və dəyişdirmə deyimini tətbiq etməyə hazırıq.

Əlavə olaraq, tapşırıq bəyanımız:

 dumb_array operator=(dumb_array other) // (1) { swap(*this, other); // (2) return *this; } 

Və bu! Bir zərbə ilə bütün üç problem dərhal həll edilir.

Niyə bu işləyir?

Birincisi, vacib bir seçimi görürük: parametrin arqumenti dəyərlə alınır. Aşağıdakıları asanlıqla edə biləsiniz (və, həqiqətən, bir çox sadəlövh deyimlər):

 dumb_array operator=(const dumb_array other) { dumb_array temp(other); swap(*this, temp); return *this; } 

Biz əhəmiyyətli bir optimizasiya fürsətini itirdik . Yalnız bu deyil, lakin bu seçim C ++ 11-də vacibdir. (Ümumiyyətlə, təlimat olduqca faydalıdır: bir funksiyaya bir şey edəcəyiksə, kompilyator onu parametr siyahısında edək. ‡)

Hər halda, bizim qaynağımızı almaq üçün bu üsul kodun çoğaltılmasının qarşısını almaq üçün əsasdır: surəti yaratmaq üçün surəti qurucusundan kodu istifadə edirik və onu təkrarlamaq lazım deyil. İndi surətin edildiyini, alış-verişə hazırıq.

Bir funksiyaya daxil olduğunuzda, bütün yeni məlumatlar artıq seçilmiş, kopyalanan və istifadə etməyə hazır olduğunu unutmayın. Bu, bizə azadlıqdan kənara çıxmağın güclü bir zəmanəti verir: əgər surətin tikintisi uğursuz olarsa funksiyaya daxil olmayacağıq və buna görə də bu vəziyyəti dəyişdirmək mümkün deyil. (Əvvəlcədən əlindən aldığımız şey, istisnanın etibarlı bir zəmanəti üçün, kompilyator bizim üçün indi xeyirli bir şeydir.)

Bu anda biz evdən swap , çünki swap imtina etməz. Verdiyimiz məlumatları kopyalanan məlumatlarla dəyişdiririk, dövlətimizi təhlükəsiz şəkildə dəyişirik və köhnə məlumatlar müvəqqəti məlumatlara çevrilir. Daha sonra funksiya geri döndüyündə köhnə məlumatlar çıxır. (Parametr sahəsinin bitməsindən və onun dağıdıcı adından sonra).

Deyim hər hansı bir kodu təkrarlamadığından, operatorda səhvlər təqdim edə bilmirik. Qeyd edək ki, bu, özünüidarəetmənin yoxlanılmasına ehtiyacın aradan qaldırılmasını nəzərdə tutur ki, bu operator= birbaşa vahid həyata keçirilməsinə imkan verir. (Əlavə olaraq, ardıcıl tapşırıqlar üçün icra cəzası yoxdur).

Və bu nüsxə və dəyişdirmə deyimidir.

C ++ 11 haqqında nə deyirsiniz?

C ++, C ++ 11-nin sonrakı versiyası resursları necə idarə etdiyimizdə bir çox mühüm dəyişiklik edir: indi üçü artıq Dördüncü Qaydasıdır (bir yarım). Niyə? Bizim resurslarımızı kopyalamaqla yanaşı , biz də hərəkət etməmiz lazımdır .

Xoşbəxtlikdən bizim üçün bu asan:

 class dumb_array { public: // ... // move constructor dumb_array(dumb_array other) : dumb_array() // initialize via default constructor, C++11 only { swap(*this, other); } // ... }; 

Burada nə baş verir? Taşıma-tikinti məqsədini xatırlayırıq: bir sinifin başqa bir nümunəsindən resurslar götürmək, təhvil verilə bilən və təhlükəli vəziyyətdə olan bir dövlətə buraxmaq.

Yəni, etdiyimiz işlər sadədir: default constructor (funksiya C ++ 11) köməyi ilə başlamaq, sonra other əvəz etmək; biz bilirik ki, klassımızın etibarlı bir nümunəsi təhlükəsiz şəkildə təyin oluna bilər və məhv edilə bilər, buna görə də other dəyişdirildikdən sonra other edə biləcəyini bilirik.

(Bəzi kompilyatorlar konstruktiv heyətə dəstək verməyəcəyini nəzərə alsaq, bu halda əl ilə default bir sinif yaratmalıyıq. Bu uğursuz, lakin xoşbəxtlikdən, çətin bir işdir.)

Niyə bu işləyir?

Bu, bizim sinifə lazım olan tək dəyişiklikdir, buna görə niyə işləyir? Parametr bir dəyər deyil, bir istinad etmək üçün etdiyimiz mühüm qərarı yadda saxla:

 dumb_array operator=(dumb_array other); // (1) 

İndi, əgər other dəyəri r ilə başlayırsa, o, səyahət istiqamətində inşa ediləcək. Böyük Eləcə də, C ++ 03 bizi kopyala qurucu üçün funksiyanı yenidən istifadə etməyə imkan verir, argumenti dəyərlə alaraq, C ++ 11 lazım olduqda hərəkət konstruktoru avtomatik olaraq seçəcəkdir. (Əlbəttə ki, əvvəlcədən əlaqəli məqalədə göstərildiyi kimi, dəyəri kopyalamaq / hərəkət etmək tamamilə aradan qaldırıla bilər.)

Və beləliklə, nüsxə və dəyişdirmə deyimidir.


Dəyişikliklər

Niyə mArray ? Çünki, şərhdə hər hansı bir əlavə kod dumb_array çağırılacaq; və əgər bu dəyər sıfır qoyulmadan baş verərsə, artıq silinmiş yaddaş silməyə çalışacağıq! Biz onu sıfıra qoyaraq bunu önləyirik, çünki null aradan qaldırılması bir əməliyyat deyil.

† Bizim std::swap üçün std::swap ixtisaslaşdığımız, sinif swap pulsuz funksiyanı swap və s. std::swap edən digər bəyanatlar var. Amma bunların hamısı zəruri deyil: swap lazımi istifadəsi qeyri-dəqiq bir çağırışdan keçəcək və funksiyamız ADL vasitəsilə tapılacaqdır. Bir funksiyanı edəcəyik.

‡ Səbəbi sadədir: özünüz üçün bir qaynaq varsa, onu dəyişdirə və ya (C ++ 11) hər hansı bir yerə köçürə bilərsiniz. Parametrlər siyahısında bir surət çıxararaq, optimallaşdırma səviyyəsini artırın.

1882
19 июля '10 в 11:43 2010-07-19 11:43 Cavab GManNickG tərəfindən 19 iyul '11 saat 11:43 'də verildi

Qəlbin tapşırığı iki addımdan ibarətdir: obyektin köhnə vəziyyətini pozmaqyeni dövlətin obyektin digər dövlətinin surəti kimi yaratmaq .

Əsasən, nə dağıdıcı , nə də qurucu Buna görə ilk fikir bu işi onlara verəcəkdir. Lakin, məhv edilməməsi baş verməsə də, tikinti edərkən biz bunu başqa bir şəkildə etmək istəyirik: əvvəlcə konstruktiv hissəni yerinə yetirək , əgər uğursuz olsa , dağıdıcı hissəni yerinə yetirsin . Kopyala və dəyişdirmə deyim yalnız bunu etmək üçün bir yoldur: birincisi, müvəqqəti yaratmaq üçün sinif instansiyasını qurucuya çağırır, sonra məlumatları müvəqqəti olaraq dəyişdirir və sonra müvəqqəti dağıdıcıya köhnə dövləti məhv etməyə imkan verir.
swap() heç vaxt uğursuz olmadığı üçün, baş verə biləcək tək hissə kopyalanır. Bu ilk edilir və əgər uğursuz olarsa, hədəf obyektində heç bir şey dəyişdirilə bilməz.

border=0

Qayğısız şəklində surəti və dəyişdirmə təyinat operatorunun parametrini təyin etməklə (istinad olmadan) bir surəti yerinə yetirməklə həyata keçirilir:

 T operator=(T tmp) { this->swap(tmp); return *this; } 
234
19 июля '10 в 11:55 2010-07-19 11:55 Cavab 19 İyul, '10 'da 11:55' də verildi. 2010-07-19 11:55

Artıq yaxşı cavablar var. Mən əsasən odur ki, mənim fikrimcə, onlar kifayət deyil - "minusların" ifadəsi idiomla "surət və dəyişdirin".

Nüsxə nədir?

Tapşırıq operatorunu dəyişdirmə funksiyası baxımından yerinə yetirmək yolu:

 X operator=(X rhs) { swap(rhs); return *this; } 

Əsas fikirdir ki:

  • bir obyektin tapşırığının ən səhv bir hissəsi yeni dövlət tərəfindən lazım olan resursları təmin etməkdir (məsələn, yaddaş, tutmalar)

  • belə ki, yeni dəyərin surəti hazırlanırsa, obyektin cari vəziyyətini dəyişdirmədən (yəni *this ) dəyişməyə cəhd etməyə cəhd edə bilərsiniz, buna görə də rhs istinad ilə müqayisədə dəyərlə (yəni, kopyalanır) qəbul edilir

  • Yerli surətinin rhs yerli nüsxəsini əvəz etməli və *this potensial uğursuzluqlar / istisnalar olmadan *this adətən, nisbətən asandır, çünki yerli nüsxədə hər hansı bir dövlətə ehtiyac yoxdur (yalnız dağıdıcıya uyğun bir dövlət tələb olunur, məsələn,> = C + + 11)

Zaman istifadə olunmalıdır? (Hansı problemləri həll edir? / Yarat) ?

  • İstədiyiniz təyin olunan obyektin istisna yaradan tapşırıqdan təsirlənməsini istəyirsinizsə, etibarlı istisna təminatına malik olan bir swap yaza bilərsiniz və ya, ideal olaraq uğursuz / throw mümkün deyildir. †

  • Əgər (daha asan) surəti konstruktoru, swap və dağıdıcı funksiyalar baxımından bir tapşırıq operatorunu müəyyən etmək üçün təmiz, aydın və etibarlı bir yola ehtiyacınız varsa.

    • "Kopyala və dəyişdirmə" kimi icra edilən öz-özünə təyinat, ümumi hallardan qaçınmaq üçün imkan verir.

  • Tapşırıq zamanı əlavə müvəqqəti obyekt ilə yaradılmış resursların istifadəsi və ya qısa müddətli istifadəsi varsa, tətbiq üçün vacib deyildir. ⁂

swap atma: bir qayda olaraq, məlumat elementlərini əvəz etmək, göstəricilər tərəfindən izlənilən obyektlərin, lakin dəyişdirmə svopları olmayan və ya X tmp = lhs; lhs = rhs; rhs = tmp; əvəz edilməli olan göstərici məlumat elementləri deyil X tmp = lhs; lhs = rhs; rhs = tmp; X tmp = lhs; lhs = rhs; rhs = tmp; və surəti qurma və ya tapşırıq atılmaqla, bəzi məlumat üzvləri dəyişdirildikdə və başqaları olmadıqda hələ də uğursuzluq şansı var. Bu potensial C ++ 03 std::string -ə də aiddir, çünki James başqa cavabı şərh edir:

@wilhelmtell: std :: string :: swap (std :: swap deyilən) ilə seçilə bilən C ++ 03-də istisnalar yoxdur. C ++ 0x, std :: string :: swap noexcept və istisnalar yaratmamalıdır. - James McNellis 22 dekabr 2010 15:24


‡ fərdi bir obyektdən təyin edildikdə ağlabatan görünən bir tapşırıq operatorunun həyata keçirilməsi, öz müqəddəratını təyin etmək üçün asanlıqla uğursuz ola bilər. İstehlakçı kodunun hətta öz müqəddəratını təyin etməyə çalışdığına inanılmaz görünsə də, x = f(x); kodu olan konteynerlərdə algo əməliyyatları zamanı nisbətən asan ola bilər x = f(x); f (bəlkə yalnız bəzi filiallar üçün #ifdef ) ala makro #ifdef #define f(x) x və ya x və ya hətta ( #ifdef səmərəsiz, lakin qısa) kodunu göstərən funksiyanı qaytarır, məsələn, x = c1 ? x * 2 : c2 ? x / 2 : x; x = c1 ? x * 2 : c2 ? x / 2 : x; ). Məsələn:

 struct X { T* p_; size_t size_; X operator=(const X rhs) { delete[] p_; // OUCH! p_ = new T[size_ = rhs.size_]; std::copy(p_, rhs.p_, rhs.p_ + rhs.size_); } ... }; 

Öz müqəddəratını təyin edərkən, yuxarıda göstərilən kod x.p_; rədd x.p_; , p_ yeni ayrılmış sahəsinə işarə edir, daha sonra bu qeyri-müəyyənləşdirilmiş məlumatları (Undefined Behavior) oxumağa çalışır, əgər bu qəribə bir şey etməzsə, hər bir yeni məhv edilmiş "T" üçün öz adını verməyə çalışır!


⁂ Dipomun "surəti və dəyişdirilməsi" əlavə vaxtın istifadəsi səbəbindən səmərəsizliyə və ya məhdudiyyətlərə səbəb ola bilər (operator operator məzmundan qurulduqda):

 struct Client { IP_Address ip_address_; int socket_; X(const X rhs) : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_)) { } }; 

Burada el yazısı Client::operator= *this rhs ilə eyni serverə bağlı olduqlarını (bəlkə də faydalı olarsa bir "sıfırlama" kodunu göndərə biləcəyini) yoxlaya bilər, surət və dəyişiklik yanaşması konstruktor nümunəsinə istinad edərsə, daha çox ehtimal ki ayrı bir yuva bağlantısı açmaq üçün yazılacaq və sonra orijinalini bağlayın. Bu, uzaqdan şəbəkə qarşılıqlı əlaqəsi deyil, həm də iş prosesində proses dəyişəninin sadə surətini çıxarmaq deməkdir, bu, resurs və socket bağlantılarında müştəri və ya serverin məhdudiyyətlərinə zidd ola bilər. (Əlbəttə ki, bu sinif kifayət qədər dəhşətli bir interfeysə malikdir, lakin bu başqa məsələdir; P).

33
06 марта '14 в 17:51 2014-03-06 17:51 Tony Delroy tərəfindən cavab March 06 '14 at 17:51 2014-03-06 17:51

Bu cavab daha yuxarıdakı cavabları əlavə etmək və bir az dəyişmək kimi.

Visual Studio bəzi versiyaları (və ehtimal ki, digər tərtibatçılar) həqiqətən cansıxıcı və mənasız bir səhv var. Buna görə də, swap funksiyasını aşağıdakı kimi elan edir / müəyyən edirsinizsə:

 friend void swap(A first, A second) { std::swap(first.size, second.size); std::swap(first.arr, second.arr); } 

... swap funksiyasını çağırdığınızda kompilyator sizə səslənəcək:

2019

20
04 сент. Cavab Oleksiy 04 sep verilir . 2013-09-04 07:50 '13 at 07:50 2013-09-04 07:50

Я хотел бы добавить слово предупреждения, когда вы имеете дело с контейнерами, поддерживающими контейнеры С++ 11-типа. Подкачка и присвоение имеют тонко различную семантику.

Для конкретности рассмотрим контейнер std::vector<T, A> , где A - некоторый тип распределения с использованием состояний, и мы сравним следующие функции:

 void fs(std::vector<T, A>  a, std::vector<T, A>  b) { a.swap(b); b.clear(); // not important what you do with b } void fm(std::vector<T, A>  a, std::vector<T, A>  b) { a = std::move(b); } 

Цель обеих функций fs и fm состоит в том, чтобы дать A состояние, в котором b было первоначально. Однако есть скрытый вопрос: что произойдет, если a.get_allocator() != b.get_allocator() ? Ответ: Это зависит. Пусть написано AT = std::allocator_traits<A> .

  • Если AT::propagate_on_container_move_assignment - std::true_type , то fm переназначает распределитель A значением b.get_allocator() , иначе это не так, и A продолжает использовать свой исходный распределитель. В этом случае элементы данных необходимо поменять отдельно, поскольку хранилище A и b несовместимо.

  • Если AT::propagate_on_container_swap - std::true_type , тогда fs заменяет как данные, так и распределители ожидаемым образом.

  • Если AT::propagate_on_container_swap - std::false_type , нам нужна динамическая проверка.

    • Если a.get_allocator() == b.get_allocator() , то два контейнера используют совместимое хранилище, а замена происходит обычным способом.
    • Однако, если a.get_allocator() != b.get_allocator() , программа имеет поведение undefined (см. [container.requirements.general/8].

Результат заключается в том, что в С++ 11 обмен файлами стал нетривиальной операцией, как только ваш контейнер начнет поддерживать контроллеры с поддержкой состояния. Это несколько "расширенный вариант использования", но это не совсем маловероятно, поскольку оптимизация перемещения обычно становится интересной только после того, как ваш класс управляет ресурсом, а память является одним из самых популярных ресурсов.

10
ответ дан Kerrek SB 24 июня '14 в 11:16 2014-06-24 11:16

Другие вопросы по меткам или Задайте вопрос