[C++] Problem med konvertering till boost::shared_ptr

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Sep 2013

[C++] Problem med konvertering till boost::shared_ptr

Hej!

Har ett litet problem som jag hoppas ni kan hjälpa mig med! Jag försöker använda mig av ett bibliotek som behöver en input i form av boost::shared_ptr men har problem med att konvertera en vektor till detta format.

Delen av mitt program som jag får problem med ser ut på följande sätt:

void test(std::vector<int> &testvec) { ... boost::shared_ptr<std::vector<int> > testvecptr(&testvec); ... }

Kompilatorn kompilerar detta utan problem men så fort jag kör programmet får jag ett felmeddelande i stil med:

*** Error in '[mappen där koden finns]': free(): invalid pointer: 0x00007ffe4e072310 ***

Jag är urkass med pointers och vet att felet finns någonstans med hur jag konverterar testvec till boost::shared_ptr. Min function void test måste dessvärre ha en pointer som invariabel för att allt annat i funktionen (samt programmet) ska fungera som det ska.

Ser ni vad jag gör för fel vid konvertering av till boost::shared_ptr som kan ge upphov till felmeddelandet ovan?

Tacksam för svar!

Main || Intel Core i7 980X @ 4.12GHz || ASUS Rampage III Gene || Corsair Vengeance 6x4GB @ 1800MHz || EVGA GTX 780 Reference || Creative Sound Blaster ZxR || 2x Intel 530 240 GB || Western Digital Blue WD10EZEX 1000 GB || ASUS VG248QE (no G-sync) ||
Laptop || Lenovo Thinkpad X220 4291-37G ||
Project: Pentium Clockbox || Intel Pentium G3258 ||

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Jun 2007

boost::shared_ptr är ju, likt std::shared_ptr, en referensräknande smart pekare. D.v.s. den övertar ägarskapet av pekaren du ger den, och anropar automatiskt delete på pekaren när antalet referenser blir 0.

I det här fallet så skapar du en shared_ptr ifrån en referens till en vector, så det kan ju t.ex. vara en stack-allokerad variabel som man inte får anropa delete på. Därav felet du får. Genom att använda en referens på det sättet så vet du inte ens om variabeln kommer leva lika länge som shared_ptr:en, så du kan få riktigt konstiga fel om du gör så.

Vad du får göra är att allokera en kopia av vectorn dynamiskt med new, t.ex.

void test(std::vector<int> &testvec) { boost::shared_ptr<std::vector<int>> testvecptr(new std::vector<int>(testvec)); }

Alternativt se till att använda en shared_ptr direkt när du skapar vectorn, för att slippa kopieringen. En annan möjlighet vore att använda en egen deleter-function till shared_ptr:en som inte gör någonting, men det känns lite tveksamt (t.ex. p.g.a. livslängden på var pekaren pekar på).

Trädvy Permalänk
Datavetare
Plats
Stockholm
Registrerad
Jun 2011

Gissar att svårigheten här främst handlar om att "&" har två rätt olika betydelser i ditt exempel.

I detta fall betyder '&' att testvec skickas som en referens.

void test(std::vector<int> &testvec) { ... boost::shared_ptr<std::vector<int> > testvecptr(&testvec); ... }

I detta fall betyder '&' att man får adressen dit där referensen testvec är lagrad. Resultatet är av typen std::vector<int>* så är inget syntax-fel i koden nedan vilket är orsaken till varför den kompilerar.

void test(std::vector<int> &testvec) { ... boost::shared_ptr<std::vector<int> > testvecptr(&testvec); ... }

Men när funktionen test() returnerar så kommer boost::shared_ptr<> anropa delete på pekaren. Fast det är ju en pekar du skapat genom att begära adressen till där något är lagrat, du har inte skapat den pekaren eller på annat sätt fått ägandeskap över minnet. Så anropet till delete&testvec kommer göra heap:en korrupt (endera anropet vid test() alt. om du faktiskt allokerat testvec med new blir heap:en korrupt när den "rätta" delete anropas.

Rätt sätt att göra detta är t.ex. så här

#include <boost/make_shared.hpp> ... void test(std::vector<int> &testvec) { ... boost::shared_ptr<std::vector<int> > testvecptr = boost::make_shared<vector<int> >(testvec); ... }

Men precis som @perost påpekar finns ingen anledning att göra detta med boost längre, allt detta är nu del av C++11. I stället för

#include <boost/make_shared.hpp> #include <boost/shared_ptr.hpp> ... boost::shared_ptr<std::vector<int> > testvecptr = boost::make_shared<vector<int> >(testvec);

Gör detta

#include <memory> ... auto testvecptr = std::make_shared<vector<int>>(testvec);

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Sep 2013

Tack för era svar, jag förstår problemet mycket bättre nu! Misstänkte att det hade något att göra med att boost:: shared_ptr anropade delete på pekaren (hade läst något om det snabbt på stackoverflow tidigare men kan inte hitta tråden längre) men förstod inte vad det innebar - förrän nu

@perost Jag har tyvärr inte möjlighet att ändra test_vector till shared_ptr utan att behöva ändra en större del av koden (vilket jag har jobbat med sedan början av februari, jag kanske borde han tänkt på det tidigare ), men att använda sig av new som du gjort är något jag ska testa. Du har dock skrivit i din kodsnutt std::shared_ptr<int> testvec; istället för vector - är detta något som krävs för att använda new på det sätt du gjort?

@Yoshman Jag läste om några lösningar på liknande problem som mitt ( bl.a. på stackoverflow) var att använda sig av make_shared men många kommenterar att detta är något man bör undvika, speciellt i samband med andra bibliotek. Stämmer detta, och om ja, är det något speciellt skäl till varför man bör undvika make_shared?

Main || Intel Core i7 980X @ 4.12GHz || ASUS Rampage III Gene || Corsair Vengeance 6x4GB @ 1800MHz || EVGA GTX 780 Reference || Creative Sound Blaster ZxR || 2x Intel 530 240 GB || Western Digital Blue WD10EZEX 1000 GB || ASUS VG248QE (no G-sync) ||
Laptop || Lenovo Thinkpad X220 4291-37G ||
Project: Pentium Clockbox || Intel Pentium G3258 ||

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Jun 2007
Skrivet av Icte:

Du har dock skrivit i din kodsnutt std::shared_ptr<int> testvec; istället för vector - är detta något som krävs för att använda new på det sätt du gjort?

Nej, jag slant lite med fingrarna bara, den biten ska vara oförändrad.

Angående make_shared så vill man nästan alltid använda make_shared istället för shared_ptr-konstruktorn om det är möjligt. Orsaken är helt enkelt att shared_ptr måste allokera ett kontrollblock för att bland annat hålla koll på hur många shared_ptr som refererar till minnet. Så shared_ptr<T>(new T(...)) kommer därför utföra två allokeringar, en för new T och en för kontrollblocket. Om man använder make_shared så allokeras allt minne som en klump, vilket är snabbare eftersom dynamisk minnesallokering är relativt långsamt.

Detta kan också vara ett problem i vissa fall, i synnerhet när man använder weak_ptr i kombination med stora objekt. Kontrollblocket kan nämligen inte tas bort så länge som det finns någon weak_ptr som använder det, även om det inte längre finns någon shared_ptr kvar. Så det kan uppstå en fördröjning från det att sista shared_ptr:en förstörs och att minnet faktiskt frigörs. Men detta är ett specialfall, i nästan alla andra fall är det bättre att använda make_shared.

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Sep 2013
Skrivet av perost:

Nej, jag slant lite med fingrarna bara, den biten ska vara oförändrad.

Angående make_shared så vill man nästan alltid använda make_shared istället för shared_ptr-konstruktorn om det är möjligt. Orsaken är helt enkelt att shared_ptr måste allokera ett kontrollblock för att bland annat hålla koll på hur många shared_ptr som refererar till minnet. Så shared_ptr<T>(new T(...)) kommer därför utföra två allokeringar, en för new T och en för kontrollblocket. Om man använder make_shared så allokeras allt minne som en klump, vilket är snabbare eftersom dynamisk minnesallokering är relativt långsamt.

Detta kan också vara ett problem i vissa fall, i synnerhet när man använder weak_ptr i kombination med stora objekt. Kontrollblocket kan nämligen inte tas bort så länge som det finns någon weak_ptr som använder det, även om det inte längre finns någon shared_ptr kvar. Så det kan uppstå en fördröjning från det att sista shared_ptr:en förstörs och att minnet faktiskt frigörs. Men detta är ett specialfall, i nästan alla andra fall är det bättre att använda make_shared.

Då hänger jag med, tack!

Har testat att köra med på make_shared och new och båda lösningar fungerar! Stort tack för hjälpen @perost @Yoshman ! Har blivit lite klokare om pointers nu

Main || Intel Core i7 980X @ 4.12GHz || ASUS Rampage III Gene || Corsair Vengeance 6x4GB @ 1800MHz || EVGA GTX 780 Reference || Creative Sound Blaster ZxR || 2x Intel 530 240 GB || Western Digital Blue WD10EZEX 1000 GB || ASUS VG248QE (no G-sync) ||
Laptop || Lenovo Thinkpad X220 4291-37G ||
Project: Pentium Clockbox || Intel Pentium G3258 ||