Натъкнах се на поредно ново 20 вчера 🙂
Една от често срещаните грешки, когато човек започва да пише на С++ е код като този :
1 2 3 4 |
int& getInt() { int i = 196; return i; } |
Псевдоним към temporary, то няма да съществува след като функцията е била извикана и това ще бъде undefined behavior (най-вероятно кофти краш).
Лесно и ясно, но.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
//credit http://goo.gl/sZn9k Alexandrescu, Sutter & Co #include <iostream> #include <string> //contains logged c'tors struct LoggedString { LoggedString(std::string rhs):str(rhs) { std::cout << "LoggedString()" << std::endl; } LoggedString(const LoggedString& rhs) { std::cout << "const LoggedString& rhs" << std::endl; str = rhs.str; } LoggedString(LoggedString&& rhs) { std::cout << "LoggedString&& rhs" << std::endl; str = std::move(rhs.str); } LoggedString& operator=(const LoggedString& rhs){ std::cout << "operator=const LoggedString& rhs" << std::endl; str = rhs.str; return *this; } LoggedString& operator=(LoggedString&& rhs) { std::cout << "operator=LoggedString&& rhs" << std::endl; str = std::move(rhs.str); return *this; } ~LoggedString() { std::cout << "~LoggedString() " << str << std::endl; } // std::string str; }; LoggedString getLoggedString() { return {"42"}; } void mostImportantConst() { const auto& magic = getLoggedString(); //(1) std::cout << magic.str << std::endl; //(2) const_cast<LoggedString&>(magic).str[0] = '5'; //(3) std::cout << magic.str << std::endl; std::cout << "**********\n"; auto antiMagic = getLoggedString(); //(4) //antiMagic = std::move(const_cast<LoggedString&>(magic)); //(5) std::cout << "**********\n"; //auto& badMagic = getLoggedString(); //(6) } int main(){ mostImportantConst(); } |
В (1) изглежда вземаме ref към temp, и в (2) следва да имаме проблем. Това обаче не е съвсем така.
С++ някъде из стандарта, ясно указва че const следва да удължи живота на temporary-тата 🙂 Без static, без копия.
Така, не само можем да изпълним (2), но (3) също работи.
(4) създаваме нов LoggedString, само за да видим по-късно кога неговият деструктор ще се извика.
Нещо повече, и двата компилатора, които имам дори не ми позволяват да компилирам (6).
Другото което видях, е че не са малко на брой редовете, които трябва да бъдат изписани за да имаме прост клас, дефиниран с всички необходими конструктори …
Output :
1 2 3 4 5 6 7 8 |
LoggedString() 42 52 ********** LoggedString() ********** ~LoggedString() 42 ~LoggedString() 52 |