Cześć!
Słowem wstępu:
Przeanalizowałem troszkę bibliotekę boost jeśli chodzi o konstruktory/operatory przenoszące dla "starego" C++ (czyli nie ma przekazywania przez &&
).
Doszedłem do wniosku, że jeśli jest wyłączona optymalizacja -fno-elide-constructors
, nie ma żadnego sposobu by taki sapis, nie kopiował danych tylko przesuwał:
Foo test(){
Foo foo;
return foo;
}
Oczywiście nie stosując referencji/wskaźników - po prostu mamy powyższy zapis i kombinujemy tak zaprojektować klasę, by dane nie były kopiowane.
Problem poruszałem w temacie: http://4programmers.net/Forum/C_i_C++/275005-konstruktor_przenoszacy
W takim razie, zostają wskaźniki, żeby oczywiście się nie pogubić, kiedy zwolnić pamięć, warto zastosować inteligentne wskaźniki.
Przeczytałem założenie takich wskaźników i postanowiłem je napisać. Nie czytałem nic więcej, tak żeby samemu pogłówkować i takie coś zaimplementować.
Po co to wszystko?
Lubię wszystko znać od podszewki, uwielbiam stare technologie i cofanie się w czasie,
pisanie systemów operacyjnych pod jednostki wyposażone w Pentium I posiadające zabójcze taktowanie np. 66MHz ;)
Ucząc się porządnie C++, chcę poznać niedoskonałości i rozwiązania jakie były wprowadzane w postaci bibliotek.
Bardzo mocno ułatwia mi to książka C++ Gotowe rozwiązania dla programistów
, gdzieś mi zawsze leżała i postanowiłem zajrzeć.
Po przeczytaniu 1.2. Kontrakty kompilacji - ograniczenia
dość mocno się do niej przekonałem i poczułem jakąś więź z C++ :)
Problem:
#include <iostream>
template<typename T>
class ptr
{
private:
void dettach(){
if((*m_c)-- == 0){
std::cout << "delete\n";
delete m_t;
delete m_c;
}
}
public:
ptr(T * t) : m_t(t), m_c(new int){
std::cout << "construct\n";
}
~ptr(){
dettach();
}
ptr(const ptr<T>& t) : m_t(t.m_t), m_c(t.m_c){
++(*m_c);
std::cout << "construct copy\n";
}
T& operator* (){ return *m_t; }
T* operator->(){ return m_t; }
ptr<T>& operator=(const ptr<T>& t){
std::cout << "operator=\n";
if(&t != this){
dettach();
m_t = t.m_t;
m_c = t.m_c;
++(*m_c);
}else
std::cout << "trolling\n";
return *this;
}
private:
T *m_t;
int *m_c; // count
};
ptr<int> alloc(){
ptr<int> ret = new int[1];
*ret = 5;
return ret;
}
int& devil(){ // glupkowate
ptr<int> ret = new int[1];
*ret = 6;
return *ret;
}
int main(){
std::cout << "Test 1\n";
{
std::cout << "\ncreate A\n";
ptr<int> a = alloc();
a=a;
std::cout << "\ncreate B\n";
ptr<int> b = alloc();
std::cout << "\na = b\n";
a = b;
std::cout << *a << std::endl;
}
std::cout << "\nTest 2\n";
{
int & c = devil();
c = 2;
}
return 0;
}
Test 1
create A
construct
construct copy
construct copy
construct copy
operator=
trolling
create B
construct
construct copy
construct copy
construct copy
a = b
operator=
delete
5
delete
Test 2
construct
construct copy
1. Jak widać w "Test 2" obiekt nie jest niszczony, to jest pierwsza ciekawostka dla mnie, kompilator jest tak cwany?
2. Da się "odróżnić" sensowne wykorzystanie operatora od głupkowatego i zablokować wywalając błąd przy kompilacji?
int& devil(){ // glupkowate
ptr<int> ret = new int[1];
*ret = 6; // ok
return *ret; // bez przesady...
}
Czytałem o metodach "wymuszania poprawności w czasie kompilacji, zwane często ograniczeniami". Lecz tutaj chyba jest to niemożliwe.
T& operator* (){ return *m_t; }
Oczywiście po napisaniu, postanowiłem poszukać jakiegoś przykładu z implementacją i po porównianiu, jest to samo
http://www.codeproject.com/Articles/15351/Implementing-a-simple-smart-pointer-in-c
Pozdrawiam serdecznie,
4user