Implementacja inteligentnych wskaźników

0

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

1
ptr(T * t) : m_t(t), m_c(new int){
        std::cout << "construct\n";
    }

Gdzie jest inicjalizacja countera? Stąd się wzięło to, że przy Test2 smart pointer nie niszczy obiektu.

if(--(*m_c) == 0){
// ...
ptr(T * t) : m_t(t), m_c(new int(1)){
  1. Nie da się.
0

OK faktycznie przeoczyłem, tylko w moim kodzie numeruję od 0, czyli:

ptr(T * t) : m_t(t), m_c(new int(0))

Dzięki!

Co do drugiego, wymyśliłem żeby operator* zwracał klasę pomocniczą która będzie miała operator przypisania itp, więc blokuje mi funkcję devil, ale z odczytaniem wartości jeszcze mały problem i dłubię dalej.

0
mwl4 napisał(a):
if(--(*m_c) == 0){

Teraz zauważyłem tą dodatkową zmianę, wsumie z nią można numerować od 1.

1

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;
}
  1. a dlaczego miałaby być optymalizacja wyłączona?
  2. żeby ten kod przesuwał zrób return move(foo);, zakładając że Foo jest przesuwalne, czyli ma konstruktor z Foo&& albo BOOST_RV_REF(Foo).
1

To teraz jeszcze 2 rzeczy:

  1. Niezgodność operatorów:
ptr<int> ret = new int[1];
delete m_t;

operator new -> operator delete, operator new[] -> operator delete[].

W STL zrobili specjalizacje dla smart pointerów:

template<class T, class Deleter = std::default_delete<T>> 
class unique_ptr;
template <class T, class Deleter> 
class unique_ptr<T[], Deleter>;
  1. Konstruktor i operator przypisania dla klasy nadrzędnej:
template <typename TYPE>
ptr(const ptr<TYPE>& t) : m_t(t.m_t), m_c(t.m_c){
        ++(*m_c);
        std::cout << "construct copy\n";
    }
template <typename TYPE>
ptr<T>& operator=(const ptr<TYPE>& 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;
    }

1 użytkowników online, w tym zalogowanych: 0, gości: 1