Portál AbcLinuxu, 6. května 2025 09:13

Dotaz: C++ shared_ptr - constrainty

3.12.2020 01:45 mokere
C++ shared_ptr - constrainty
Přečteno: 531×
Odpovědět | Admin
Mám 2 pointery ukazujúce na 2 objekty (iterator a stream). Iterátor prechádza stream. Jeden z tých pointerov (iterator) vracia funkcia ReadLines. Ten druhý by sa mal uvolniť spolu s tým prvým. Ako na to? Skúmal som dokumentáciu a našiel som tam parameter konštruktora Deleter ktorý som využil na zmazanie oboch pointerov vo chcíli keď by sa mal zmazať smart pointer ukazujúci na iterator:
namespace
{
	template<typename CharType = wchar_t>
	std::basic_istream<CharType>& operator>>(std::basic_istream<CharType>& inputStream, std::basic_string<CharType>& line)
	{
		std::getline(inputStream, line);
		return inputStream;
	}
}

template<typename CharType = char>
auto ReadLines(std::wstring path)
{
	auto inputStream = new std::ifstream(path);
	using iterator = std::istream_iterator<std::basic_string<CharType>>;
	auto begin = std::shared_ptr<iterator>(
		new iterator(*inputStream),
		[inputStream](auto* iterator)
		{
			delete iterator;
			delete inputStream;
		}
	);
	return begin;
}
Je toto správne riešenie? Alebo na to idem zle? Lebo fungovať mi to funguje. Ak viete o niečom priamočiarejšom tak sem s tým. Ďakujem.
Nástroje: Začni sledovat (0) ?Zašle upozornění na váš email při vložení nového komentáře.

Odpovědi

3.12.2020 01:53 mokere
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Odpovědět | | Sbalit | Link | Blokovat | Admin
A takto sa potom volá funkcia ReadLines:
auto begin = ReadLines(L"c:\test\newFile21.txt");
for (auto it = *begin; it != std::istream_iterator<std::string>(); ++it)
{
	std::cout << ":" << *it << "n";
}
3.12.2020 01:56 mokere
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
nejako blbne escapovanie, namiesto n tam malo byť lomitko a n resp std::endl
3.12.2020 09:20 MadCatX | skóre: 28 | blog: dev_urandom
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Odpovědět | | Sbalit | Link | Blokovat | Admin
#include <fstream>
#include <iostream>
#include <stdexcept>

template <typename T>
void read_lines(const std::string &path, T &&handler)
{
        std::ifstream s{};
        s.open(path);
        if (!s.is_open())
                throw std::runtime_error{"Cannot open file"};

        std::string str{};
        while (std::getline(s, str).good())
                handler(str);
}

int main()
{
        try {
                read_lines("abc.txt", [](const std::string &s) { std::cout << s << std::endl; });
        } catch (const std::runtime_error &ex) {
                std::cout << ex.what() << std::endl;
        }

        return 0;
}
3.12.2020 10:50 10minuteman
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Odpovědět | | Sbalit | Link | Blokovat | Admin
Obecne, jak mas nekde nahe new & delete, vis, ze to nepises dobre. Spravne je vyuzit RAII a vytvorit si nejakou guard tridu: new volas v konstruktoru, delete v destruktoru. Pak mas jistotu, ze se spravne uvolni tvuj resource i pri nestandardnim ukonceni scope (treba pri vyhozeni vyjimky). No ale jelikoz std typy tak uz funguji (tedy svuj resource si hlida uz treba ten ifstream), nemusis to nijak resit a napis to tak, jak ti radi MadCatX.

Za druhe, std::shared_ptr (a podobne veci) inicializuj pomoci std::make_shared (a podobnych funkci) - je to bezpecnejsi a rychlejsi.
3.12.2020 19:07 Andrej | skóre: 51 | blog: Republic of Mordor
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Odpovědět | | Sbalit | Link | Blokovat | Admin

Začal bych od jednoduchého příkladu — implicitní iterace po slovech. To se zařídí třeba takhle:

#include <fstream>
#include <iostream>
#include <iterator>
#include <string>

namespace {

template<typename C>
struct ifstream_iterable {
  ifstream_iterable(const std::string &path) : stream_{path} {}
  auto begin() { return iterator{stream_}; }
  auto end() { return iterator{}; }
 private:
  typedef std::istream_iterator<std::basic_string<C>, C> iterator;
  std::basic_ifstream<C> stream_;
};

}  // namespace

int main() {
  for (auto &w : ifstream_iterable<wchar_t>{"/proc/cpuinfo"})
    std::wcout << w << std::endl;
}

To bychom měli. Teď je otázka, jak z toho^^^ udělat iteraci po řádcích. Předně pár poznámek k tématu:

Co tedy podniknout? Třeba tohle:

#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>

namespace {

template <typename C>
struct line {
  std::basic_istream<C>& operator <<(std::basic_istream<C> &stream) {
    std::getline(stream, str_);
    return stream;
  }
  operator const std::basic_string<C>&() const & { return str_; }
  operator std::basic_string<C>&() & { return str_; }  // zde se nepoužije
  operator std::basic_string<C>() && { return std::move(str_); }  // -dtto-
 private:
  std::basic_string<C> str_;
};

template <typename C>
std::basic_istream<C>& operator >>(std::basic_istream<C> &stream,
                                   line<C> &str) {
  str << stream;
  return stream;
}

template<typename C>
struct ifstream_iterable {
  ifstream_iterable(const std::string &path) : stream_{path} {}
  auto begin() { return iterator{stream_}; }
  auto end() { return iterator{}; }
 private:
  typedef std::istream_iterator<line<C>, C> iterator;
  std::basic_ifstream<C> stream_;
};

}  // namespace

int main() {
  for (const std::wstring &l : ifstream_iterable<wchar_t>{"/proc/cpuinfo"})
    std::wcout << l << std::endl;
}

Tohle^^^ nedědí od základních typů, nevyvolává příliš mnoho nespecifikovaných rohových případů, nepoužívá kámoše a vystačí si s jednou třídou (dělnickou), kterou lze konvertovat na odpovídající string. (Pro složitější konverze do jiného API si lze napsat deduction guides, ale to už je jiné téma. V této podobě se kód přeloží s C++ 14, 17 i 20, zatímco s deduction guides by vyžadoval minimálně C++17.)

Ještě závěrem dodám (a předejdu tak, doufám, některým komentářům), že někteří lidé mají utkvělou špatnou představu o sousloví return std::move(...); — myslí si, že něco takového nedává smysl a že by to nemělo existovat. Samozřejmě se mýlí, jak jinak; tady jsou k tomu předdrobnosti i podrobnosti.

3.12.2020 19:17 Andrej | skóre: 51 | blog: Republic of Mordor
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty

A když nad tím znova přemýšlím (což rozhodně nedělám často), čitelnější to bude bez toho nestandardního operátoru <<, který nijak neinteraguje s (kni)hovnou a jenom všeho všudy mate čtenáře (včetně mě). Takže raději třeba takto:

#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>

namespace {

template <typename C>
struct line {
  operator const std::basic_string<C>&() const & { return str_; }
  operator std::basic_string<C>&() & { return str_; }
  operator std::basic_string<C>() && { return std::move(str_); }  // nepoužito
 private:
  std::basic_string<C> str_;
};

template <typename C>
std::basic_istream<C>& operator >>(std::basic_istream<C> &stream,
                                   line<C> &str) {
  std::getline(stream, static_cast<std::basic_string<C>&>(str));
  return stream;
}

template<typename C>
struct ifstream_iterable {
  ifstream_iterable(const std::string &path) : stream_{path} {}
  auto begin() { return iterator{stream_}; }
  auto end() { return iterator{}; }
 private:
  typedef std::istream_iterator<line<C>, C> iterator;
  std::basic_ifstream<C> stream_;
};

}  // namespace

int main() {
  for (const std::wstring &l : ifstream_iterable<wchar_t>{"/proc/cpuinfo"})
    std::wcout << l << std::endl;
}
5.12.2020 17:07 mokere
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Odpovědět | | Sbalit | Link | Blokovat | Admin
Ďakujem Vám všetkým. Aj za to že ste mi opravili chybu s riadkami vs slovami. To bola moja nepozornosť. A popravde som si ju ani nevšimol.

Každopádne MadCatX to vymyslel zaujímavo.

Ja som chcel ale napodobniť funkciu ReadLines z .NETu https://docs.microsoft.com/en-us/dotnet/api/system.io.file.readlines?view=net-5.0 ktorá vracia IEnumerable (obsahujúca Enumerator).

Andrej To čo ste urobili vy je presne to čo som hľadal. Za čo Vám veľmi pekne ďakujem.

10minuteman Čo sa týka new a delete snažím sa ich používať minimálne. Ale ak pri smartpointeroch používate deleter tak sa použitiu delete nevyhnete. Veď aj smart pointery v konečnom dôsledku volajú delete.

5.12.2020 22:17 Andrej | skóre: 51 | blog: Republic of Mordor
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
Ale ak pri smartpointeroch používate deleter tak sa použitiu delete nevyhnete.

To je sice pravda, ale právě nadužívání dynamické alokace je u C++ poměrně častá chyba. Zrovna třeba statement delete iterator; není dobrý nápad (a kdekoho přiměje zvednout obočí), protože iterátor má být (a většinou je) malý kousek dat (něco jako 16 B, dejme tomu), který se dá a má předávat všude hodnotou. (Samozřejmě kromě případu, kdy je potřeba z více míst měnit jeden iterátor, tj. mít něco jako dvouhvězdičkový pointer.)

Ještě bych poznamenal, jen tak pro úplnost, že příklad v dotazu by byl lepší s použitím std::unique_ptr místo std::shared_ptr. Zatímco std::unique_ptr je jednoduchý zapouzdřený pointer, který vyjadřuje exkluzivní vlastnictví a vlastní z jednoho kontextu jeden kousek dat, std::shared_ptr je složitá mašinerie s atomickým reference-countingem. Uvnitř v implementaci std::shared_ptr neukazuje přímo na data, která spravuje, nýbrž na svou vlastní dynamickou strukturu, ve které má (kromě pointeru) také atomické počítadlo referencí a pár dalších vychytávek. (Dlužno navíc dodat, v souvislosti s atomickým počítadlem, že jedna atomická instrukce může stát čas srovnatelný s řádově tisícem sčítání.) Dá se říct, že std::shared_ptr je v jistém smyslu thread-safe, byť s omezeními:

  • Několik instancí std::shared_ptr, každou z jiného vlákna, lze používat paralelně zcela bez omezení.
  • Použití jedné instance std::shared_ptr několika vlákny (například rozumně atomické přiřazení toho sdíleného pointeru) ovšem samo od sebe atomické není; k tomu slouží specializace std::atomic<std::shared_ptr<...>>.

Zkrátka a dobře (nebo zdlouha a špatně, teď nevím),

  • kdykoliv je možné vyhnout se přímému použití pointerů, je třeba této příležitosti využít. Všechny kontejnery mají na zásobníku jenom něco velmi malého (jako třeba fixních 16 B nebo tak) a dynamickou alokaci si spravují samy.
  • kdykoliv je možné použít std::unique_ptr místo std::shared_ptr, je to jasná volba, zejména z hlediska efektivity a jednoznačnosti vlastnictví objektů. Atomický reference-counting je potřebný jenom velmi zřídka, nikoliv zhusta.
6.12.2020 15:21 10minuteman
Rozbalit Rozbalit vše Re: C++ shared_ptr - constrainty
No to je prave ono: nepsat to rucne, ale nechat to za sebe resit objekty. Jasne, ze interne k new a delete treba dojde. Alokaci na halde se nejspis nevyhnes. Kdyz to ale za tebe bude resit objekt, mas pak mnohem mene starosti.

Založit nové vláknoNahoru

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.