Najlepszy sposób na zwalnianie pamięci

0

Witam
Mam metodę inicjalizującą która korzysta z jakiejśtam dostępnej free powszechnie używanej w świecie libki np. sd-busa (służącego do komunikacji z systememd w linuxie i obsługiwanymi przez niego procesami). Często w takich 3rd party libkach aby uzyskać dostęp do jakiejś funkcjonalności trzeba wywołać pod rząd szereg metod z ktorych każda może zwracać errorCode, zainicjować obiekt ktory należałoby zwolnić po zakończeniu programu lub w przypadku niepowodzenia. Często też te 3rd party libki nie korzystają z C++11/14 więc nie da się zastosować inteligentnych wskaźników. Tutaj przykład kodu:

void init()
{
    Typ1* wskaDoBusa = NULL;
    Typ2* wskDoEventa= NULL;
    Typ3* wskDoSubskrypcji= NULL;

     int r = openBus(&wskDoBusa);
     if(r < 0) {zwolnijPamiec(&wskDoBusa, &wskDoEventa, &wskDoSubskrypcji), wyjdz z programu}

      r = openEvent(&wskDoEventa)
     if(r < 0) {zwolnijPamiec(...), wyjdz z programu}

     r = powiążEventZBusem(&wskDoBusa, &wskDoEventa)
     if(r < 0) {zwolnijPamiec(...), wyjdz z programu}

    r = zasubskrybujSieNaCos(&wskDoBusa, &wskDoEventa, &wskDoSubskrypcji)
     if(r < 0) {zwolnijPamiec(...), wyjdz z programu}

    r = podlaczSieDoPetliMonitorującej(&wskDoBusa, &wskDoEventa, &wskDoSubskrypcji)
     if(r < 0) {zwolnijPamiec(...), wyjdz z programu}

    ...i tak jeszcze z 2-3 metody by uzyskać dostęp do pewnej funkcjonalności
}

To co wyżej nie jest dobre bo może pojawić się jakiś delikwent który przy implementacji jakiegoś ficzera wymagającego dodania kolejnego ifa zapomni wywołać metodę zwalniającą pamięć, da tylko return i mamy memory leaka. Prawdziwy przykład z życia, w pracy ktoś tak zrobił i urządzenie po ok. 60 dniach się resetowało jako że brakło pamięci.

Inne rozwiązanie to w każdym z if-ów robić throw a następnie za ostatnią metodą łapać wyjątek i po bloku try{}catch{} wywołać 1 raz metodę zwolnijPamięć():

void init()
{
try{
     wywołania metod jak wyżej z tym że robimy if(r<0) {throw wyjatek}
}
catch(...)
{}
zwolnijPamiec(...)
}

Inne jeszcze rozwiązanie to użycie goto:

void init()
{
...
// wywołania metod jak wyżej z tym że robimy if(r<0) {goto: finish}
...
finish:
    zwolnijPamiec(...)
}

Ale goto jest powszechnie wyśmiewanym patternem.
Jaki sposób jest najbardziej optymalny tutaj?

0
zerniki male napisał(a):

Inne jeszcze rozwiązanie to użycie goto:

void init()
{
...
// wywołania metod jak wyżej z tym że robimy if(r<0) {goto: finish}
...
finish:
    zwolnijPamiec(...)
}

Ale goto jest powszechnie wyśmiewanym patternem.

Chciałeś raczej powiedzieć "powszechnie używany".

0

tu jeszcze 4 pomysł:

void init()
{
    Typ1* wskaDoBusa = NULL;
    Typ2* wskDoEventa= NULL;
    Typ3* wskDoSubskrypcji= NULL;

     int r = openBus(&wskDoBusa);
     if(r >=0)
            r = openEvent(&wskDoEventa)
      if(r>=0)
             r = powiążEventZBusem(&wskDoBusa, &wskDoEventa)
      if(r>=0)
             r = zasubskrybujSieNaCos(&wskDoBusa, &wskDoEventa, &wskDoSubskrypcji)
      itp.
    
   zwolnijPamiec(&wskDoBusa, &wskDoEventa, &wskDoSubskrypcji)
 
}
0

nie, powszechnie wyśmiewany, zły, że zaburza widzenie kodu, że ten kto używa goto nie umie programować, takie opinie krążą wśród programistów:)

0
zerniki male napisał(a):

nie, powszechnie wyśmiewany, zły,

Co nie zmienia faktu, że jest powszechnie używany. I często optymalny.

że zaburza widzenie kodu,

Chyba analfabetom. To znany konstrukt.

że ten kto używa goto nie umie programować

Powiedz to Linusowi Torvaldsovi, Theo de Raadt, Ericowi S. Reymodnowi i tysiącom innych programistów C i C++.

Ten kto nie umie używać goto sensownie nie umie programować. To powszechnie stosowana metoda zwalniania kolejnych bloków pamięci w sterownikach, systemach operacyjnych, systemach bare metal, programach coreutils, serwerach WWW, oprogramowaniu całej masy urządzeń wbudowanych, grach komputerowych itp. gdzie trzeba zarządzać pamięcią ręcznie, szybko i efektywnie bez zdawania się na nieprzewidywalność garbage collectora. Występuje w milonach linii kodu.

, takie opinie krążą wśród programistów:)

Wśród programistów krąży wiele bezsensownych opinii i poglądów.

1
zerniki male napisał(a):

Często też te 3rd party libki nie korzystają z C++11/14 więc nie da się zastosować inteligentnych wskaźników.

To czy libki korzystają czy też nie z C++11/14 nie ma nic wspólnego z tym, czy mógłbyś tutaj użyć smart pointera. Smart pointer możesz użyć zawsze, tylko po prostu musisz zdefiniować własny deleter.

Przykład:

#include <iostream>
#include <memory>

using namespace std;

struct bus_t
{
	bus_t()
	{
		cout << "bus_t()" << endl;
	}
	
	~bus_t()
	{
		cout << "~bus_t()" << endl;
	}
};

int open_bus(bus_t **bus)
{
	*bus = new bus_t;
	return 0;
}

int free_bus(bus_t *bus)
{
	delete bus;
	return 0;
}

struct bus_deleter
{
  void operator()(bus_t *bus)
  {
  	free_bus(bus);
  }
};

int my_open_bus(unique_ptr<bus_t, bus_deleter> &bus)
{
	bus_t *temp = nullptr;
	int ret_code = open_bus(&temp);
	bus = std::move(decltype(bus)(temp));
	return ret_code;
}

bool foo()
{
	unique_ptr<bus_t, bus_deleter> bus;
	if(my_open_bus(bus) != 0)
	{
		return false;
	}
	
	// jeśli nie chcesz implementować własnego open_bus, to:
	unique_ptr<bus_t, bus_deleter> bus2;
	{
		bus_t *bus_temp = nullptr;
		if(open_bus(&bus_temp) != 0)
		{
			return false;
		}
		bus2 = decltype(bus2)(bus_temp);
	}
	return true;
}

int main() {
	foo();
	return 0;
}

https://ideone.com/WVSlNK

0
zerniki male napisał(a):

Często też te 3rd party libki nie korzystają z C++11/14 więc nie da się zastosować inteligentnych wskaźników.

Oczywiście, że się da, tyle, że nie dostajesz tego za darmo. Wystarczy dopisać troszkę opakowywaczy i po problemie.
Troszkę wysiłku, który się szybko zwraca, w postaci prostszego i bardziej niezawodnego kodu właściwego.

Problem polega na tym, że podałeś jakiś bzdety przykład, więc trudno dać coś sensownego. Ja kiedyś napisałem coś takiego:

template<class T>
class CustomDeleter
{
public:
     void operator()(T* pX)
    {
        std::default_delete<T>(pX);
    }
};
 
template<class T, class D = CustomDeleter<T>>
using CustomDeleterPtr = std::unique_ptr<T, D>;
 
template<class T, class D = CustomDeleter<T>>
auto WrapWithUnique(T* x) -> std::unique_ptr<T, D>
{
    return std::unique_ptr<T, D>(x);
}
 
#define CUSTOM_DELETER_SMART_POINTER(SomeType, SomeTypeDeleter) \
template<> \
class CustomDeleter<SomeType> \
{ \
    public: \
    using pointer = T*; \
    void operator()(pointer pX) \
    { \
        SomeTypeDeleter(pX); \
    } \
}; \
 \
using SomeType ## _UniquePtr = CustomDeleterPtr<SomeType>;

#define CUSTOM_RELEASE_HANDLE(SomeType, SomeHandlerRelease) \
template<> \
class CustomDeleter<SomeType> \
{ \
    public: \
    using pointer = SomeType; \
    void operator()(pointer pX) \
    { \
        SomeHandlerRelease(pX); \
    } \
}; \
 \
using SomeType ## _UniqueHandle = CustomDeleterPtr<SomeType>;

Lekko poprawiając powyższe makro można nawet obsłużyć uchwyty windows-a. Przy czym trzeba uważać, żeby sobie w stopę nie strzelić szablonami, bo uchwyty tak naprawdę są jednego typu int.

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