Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?
Så varför använder Volvo, BMW, GM och de andra stora biltillverkarna C++ 14/17 för bl.a. förarassistanssystem som har semi-hårda realtidskrav? Vad gör C++ till ett dåligt val här?
Jag vet inte vad de använder, eller vilka krav och begränsningar de har, eller varför de har gjort de val de har.
Vad gäller inbyggda system så är man ju ofta begränsad av vad de tillgängliga utvecklingsverktygen har stöd för. C-kompilatorer finns för i stort sett alla processorer. C++-kompilatorer till nästan lika många. Andra språk kanske inte finns tillgängliga, och då är de inte mycket till alternativ.
Raw pointers (eller bara pekare) är vad C++ alltid har haft, och det finns inget fel med det. Det är lågnivå av en anledning och krävs en del kompetens för att inte göra misstag, men det är så det är tänkt från början. Unique_ptr är en krycka för n00bs som vill hävda sig vara grymma programmerare. Det tillför ingenting annat än onödig komplexitet och en inbillad prestige.
Istället för att fråga varför pekare är bättre än "smarta pekare", ställ dig frågan varför behövs ens "smarta" pekare? Bortsett från shared_ptr och weak_ptr som kan vara användbara (dock i min åsikt så bör man oftast själv implementera sådan funktionalitet om man behöver det, då shared/weak är extremt generiska implementationer).
Jag tror du tar i när du skriver "det är så det är tänkt från början". Det är nog inte så att Bjarne tänkte att C++ skulle ha raw pointers och att det var så man skulle jobba. De ärvdes helt enkelt från C. De smarta pekarna handlar om att göra det svårare att göra fel och där fyller till och med unique_ptr ett syfte. Det är solklart vem som äger objektet och det kommer automatiskt deallokeras när det går ut scope.
Färre saker jag slipper tänka på som programmerare gör livet enklare. Är implementationen av de smarta pekarna komplexare än att köra raw pointers, javisst, men det är ett pris de flesta av oss är villiga att betala för att vi slipper göra delete och ingen kommer oavsiktligt accessa ett dött objekt. Är det så prestandakritiskt att du inte vill betala det priset finns get() så är det precis lika snabbt och (o)säkert som din kod med raw pointers.
Försök inte laga något som inte är trasigt, säger jag bara
Du kan fortsätta att använda new och delete. Det funkar fortfarande
Jag tror du tar i när du skriver "det är så det är tänkt från början". Det är nog inte så att Bjarne tänkte att C++ skulle ha raw pointers och att det var så man skulle jobba. De ärvdes helt enkelt från C. De smarta pekarna handlar om att göra det svårare att göra fel och där fyller till och med unique_ptr ett syfte. Det är solklart vem som äger objektet och det kommer automatiskt deallokeras när det går ut scope.
Färre saker jag slipper tänka på som programmerare gör livet enklare. Är implementationen av de smarta pekarna komplexare än att köra raw pointers, javisst, men det är ett pris de flesta av oss är villiga att betala för att vi slipper göra delete och ingen kommer oavsiktligt accessa ett dött objekt. Är det så prestandakritiskt att du inte vill betala det priset finns get() så är det precis lika snabbt och (o)säkert som din kod med raw pointers.
Du kan fortsätta att använda new och delete. Det funkar fortfarande
Hade bjarne haft något annat i åtanke så hade bjarne implementerat det.
Jag förstår syftet med unique_ptr's, jag gillar det bara inte. Det uppmuntrar amatörmässig kod, och har således en kontraproduktiv effekt. Det är som sagt dessutom bara stödhjul. Nej, man behöver inte använda det, men det behöver faktiskt inte existera till att börja med.
Allt som hanterar minne i C och C++ arbetar med "råa pekare" på något lager. Allt ovanpå är helt enkelt extra komplexitet. Ibland är den komplexiteten en kostnad för funktionalitet som är befogad och bra, men i detta fallet är det en kostnad som kodmässigt inte tillför någon funktionalitet. Det tillför dock som du säger något till programmerare som väljer att vara lata och inkompetenta, men det är viktigt att komma ihåg att det har en kostnad för den resulterande kodkvaliteten också.
Hade bjarne haft något annat i åtanke så hade bjarne implementerat det.
Var drar du gränsen? Är det C++98 man skall skriva för att inte vara lat och inkompetent? Eller skall vi backa till Cfront eller kanske C with classes? Bjarne är fortfarande högst aktiv i språkutvecklingen och det tyder väl på att allt som Bjarne ville åstadkomma kanske inte var implementerat i första versionen?
Jag förstår syftet med unique_ptr's, jag gillar det bara inte. Det uppmuntrar amatörmässig kod, och har således en kontraproduktiv effekt. Det är som sagt dessutom bara stödhjul. Nej, man behöver inte använda det, men det behöver faktiskt inte existera till att börja med.
Allt som hanterar minne i C och C++ arbetar med "råa pekare" på något lager. Allt ovanpå är helt enkelt extra komplexitet. Ibland är den komplexiteten en kostnad för funktionalitet som är befogad och bra, men i detta fallet är det en kostnad som kodmässigt inte tillför någon funktionalitet. Det tillför dock som du säger något till programmerare som väljer att vara lata och inkompetenta, men det är viktigt att komma ihåg att det har en kostnad för den resulterande kodkvaliteten också.
Varför är det amatörmässigt att nyttja finesserna som finns i språket? Jag har programmerat C++ sedan slutet på 90-talet. Jag vet precis vad smarta pekare kostar i form av mera minne och extra cykler och det priset betalar jag gärna. Vad då inte tillför funktionalitet? Det är ju hela poängen med smart pointers, de beter sig som vanliga pekare men jag slipper komma ihåg att göra saker och jag slipper minnesfel. Hur kan detta vara dåligt?
På vilket sätt skulle du säga att smarta pekare "har en kostnad för den resulterande kodkvaliteten"? Färre minnesfel och enklare kod skulle jag säga förbättrar kodkvaliteten.
Jag tycker din attityd lutar åt att somliga "vill hävda sig vara grymma programmerare"
/** ---------------------------------------------------------------------------
* @brief get all values for name as variant's in list
* @param stringName collected values for name
* @return std::vector<gd::variant> list of variants with values from name
*/
std::vector<gd::variant> options::get_variant_all( const std::string_view& stringName ) const
{ assert( stringName.empty() == false );
auto values_ = m_argumentsValue.get_argument_all( stringName ); // get list with argument't
auto variants_ = gd::argument::arguments::get_variant_s( values_ ); // convert list with argument's to list of variant's
return variants_;
}
/** ---------------------------------------------------------------------------
* @brief Read uuid from uuid formated string
* Supported formats are `00000000000000000000000000000000`, `00000000-0000-0000-0000-000000000000`
* or `{00000000-0000-0000-0000-000000000000}`.
* @param puBegin start of uuid formated string value
* @param puEnd end of uuid formated string value
* @param puSet uuid buffer for converted hex string
*/
inline void uuid::read_s( const value_type* puBegin, const value_type* puEnd, value_type* puSet )
{
auto uLength = puEnd - puBegin; assert( uLength == 32 || uLength == 36 || uLength == 38 );
if( uLength == 32 )
{
for( ; puBegin != puEnd; puBegin += 2 )
{ assert( *puBegin < sizeof(m_pHexValue_s) ); assert( *(puBegin + 1) < sizeof(m_pHexValue_s) );
*puSet = m_pHexValue_s[*puBegin] << 4; assert( m_pHexValue_s[*puBegin] != 0 || *puBegin == '0' );
*puSet += m_pHexValue_s[*(puBegin + 1)]; assert( m_pHexValue_s[*(puBegin + 1)] != 0 || *(puBegin + 1) == '0' );
puSet++;
}
}
else
{
if( uLength == 38 )
{
puBegin++;
puEnd--;
}
while( puBegin != puEnd )
{
if( *puBegin != '-' )
{ assert( *puBegin < sizeof( m_pHexValue_s ) ); assert( *(puBegin + 1) < sizeof( m_pHexValue_s ) );
*puSet = m_pHexValue_s[*puBegin] << 4; assert( m_pHexValue_s[*puBegin] != 0 || *puBegin == '0' );
*puSet += m_pHexValue_s[*(puBegin + 1)]; assert( m_pHexValue_s[*(puBegin + 1)] != 0 || *(puBegin + 1) == '0' );
puSet++;
puBegin += 2;
}
else
{ assert( (puEnd - puBegin) == 28 || (puEnd - puBegin) == 23 || (puEnd - puBegin) == 18 || (puEnd - puBegin) == 13 );
puBegin += 1;
}
}
}
}
/// 256 word values with bits set to mark different character classes used in parse logic
/// 0x0020 = CHAR_GROUP_DECIMAL | CHAR_GROUP_SCIENTIFIC
/// 0x04E2 = CHAR_GROUP_ALNUM | CHAR_GROUP_SCIENTIFIC | CHAR_GROUP_HEX | CHAR_GROUP_DECIMAL | CHAR_GROUP_DIGIT (number)
/// 0x0404 = CHAR_GROUP_ALNUM | CHAR_GROUP_ALPHABET
/// 0x0444 = CHAR_GROUP_ALNUM | CHAR_GROUP_HEX | CHAR_GROUP_ALPHABET
/// 0x04E4 = CHAR_GROUP_ALNUM | CHAR_GROUP_SCIENTIFIC | CHAR_GROUP_HEX | CHAR_GROUP_DECIMAL | CHAR_GROUP_ALPHABET
/// 0x0010 = CHAR_GROUP_QUOTE (quote)
constexpr uint16_t puCharGroup_g[0x100] =
{
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0001,0x0001,0x0000,0x0001,0x0000,0x0000,0x0000, /* 0x00-0x0F */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0x10-0x1F */
0x0001,0x0000,0x0010,0x0000,0x0000,0x0000,0x0000,0x0010,0x0000,0x0001,0x0008,0x0008,0x0000,0x0008,0x00A0,0x1008, /* 0x20-0x2F ,!,",#,$,%,&,',(,),*,+,,,-,.,/ */
0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x04E2,0x0000,0x0000,0x0008,0x0008,0x0008,0x0000, /* 0x30-0x3F 0,1,2,3,4,5,6,7,8,9,:,;,<,=,>,? */
0x0000,0x0444,0x0444,0x0444,0x0444,0x04E4,0x0444,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404, /* 0x40-0x4F @,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O */
0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0008,0x1000,0x0000,0x0008,0x0008, /* 0x50-0x5F P,Q,R,S,T,U,V,W,X,Y,Z,[,\,],^,_ */
0x0000,0x0444,0x0444,0x0444,0x0444,0x04E4,0x0444,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404, /* 0x60-0x6F `,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o */
0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0404,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0x70-0x7F p,q,r,s,t,u,v,w,x,y,z,{,|,},~*/
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0x80-0x8F */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0x90-0x9F */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xA0-0xAF */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xB0-0xBF */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xC0-0xCF */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xD0-0xDF */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xE0-0xEF */
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, /* 0xF0-0xFF */
};
Och här är ett exempel på att det inte är så lätt att göra C++ "minnessäkert".
Kompilerade ditt exempel med följande flaggor
➜ clang++ -fsanitize=address -fno-omit-frame-pointer -O2 -std=c++14 -o main main.cpp
➜ ./main
D.v.s. har kvar assert() check:ar! Dessa triggar inte nödvändigtvis om man skickar in en sträng av accepterad längd ("accepterad" här är att den inte triggar assert, fel längd kraschar ju programmet vilket kanske är "sådär"...)
Om man gör något som en assert plockar får man t.ex. detta
./main
Assertion failed: (m_pHexValue_s[*(puBegin + 1)] != 0 || *(puBegin + 1) == '0'), function read_s, file main.cpp, line 51
men i detta fall är det fullt möjligt att komma runt det i vissa lägen och skriva där man inte riktigt borde få skriva -> typisk minnesproblem som alla "minnes-säkra" fångar men C++ (och C) inte nödvändigtvis hanterar.
=================================================================
==9070==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00016f4f71d0 at pc 0x00010090b93c bp 0x00016f4f7180 sp 0x00016f4f7178
WRITE of size 1 at 0x00016f4f71d0 thread T0
#0 0x10090b938 in read_s(char const*, char const*, unsigned char*)+0x588 (main:arm64+0x100003938)
#1 0x10090af60 in main+0x100 (main:arm64+0x100002f60)
#2 0x19906c270 (<unknown module>)
Address 0x00016f4f71d0 is located in stack of thread T0 at offset 48 in frame
#0 0x10090ae6c in main+0xc (main:arm64+0x100002e6c)
This frame has 1 object(s):
[32, 48) 'puSet' <== Memory access at offset 48 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (main:arm64+0x100003938) in read_s(char const*, char const*, unsigned char*)+0x588
Shadow bytes around the buggy address:
0x00016f4f6f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f6f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x00016f4f7180: 00 00 00 00 f1 f1 f1 f1 00 00[f3]f3 00 00 00 00
0x00016f4f7200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00016f4f7400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==9070==ABORTING
[1] 9070 abort ./main
Detta kan triggas om input har rätt längd och så, men där det finns ett udda antal xdigits i ett block.
I detta fall pekar "puSet" på en buffert som är tillräckligt stor för att lagra ett giltig UUID-värde
Och här är ett exempel på att det inte är så lätt att göra C++ "minnessäkert".
Kompilerade ditt exempel med följande flaggor
Detta kan triggas om input har rätt längd och så, men där det finns ett udda antal xdigits i ett block.
I detta fall pekar "puSet" på en buffert som är tillräckligt stor för att lagra ett giltig UUID-värde
Det "kostar" att kontrollera minne om du inte behöver det.
Allt beror på hur det används och om minne används internt så varför skall det kontrolleras?
men jag slipper komma ihåg att göra saker och jag slipper minnesfel
precis så här. uppmuntrar slappa lata och ibland inkompetenta utvecklare, tror du verkligen att den resulterade koden blir gedigen med denna approach? skill issue att få minnes problem för att man inte använder smarta pekare dessutom, sånt sitter i ryggmärgen när man kodat ett tag.
låter denna kommentar sätta punkt på denna diskussionen för mig. vill du vara lat så kör hårt själv föredrar jag att göra saker ordentligt. enda sättet att få robust kod påriktigt..
Du använder fortfarande en väldigt märklig, och närmast unik, definition av "deklarativ". I den betydelse ordet normalt har när det gäller programmering så har det ingen som helst koppling till domän-specifik kod.
Deklarativ kod kan vara domän-specifik, eller generell. Imperativ kod kan vara domän-specifik, eller generell. Ingendera är bättre för det ena eller det andra.
Här är en text om deklarativ och imperativ (poängen med varför skriva deklarativ kod)
Imperative vs declarative
De ärvdes helt enkelt från C. De smarta pekarna handlar om att göra det svårare att göra fel och där fyller till och med unique_ptr ett syfte. Det är solklart vem som äger objektet och det kommer automatiskt deallokeras när det går ut scope.
Färre saker jag slipper tänka på som programmerare gör livet enklare. Är implementationen av de smarta pekarna komplexare än att köra raw pointers, javisst, men det är ett pris de flesta av oss är villiga att betala för att vi slipper göra delete och ingen kommer oavsiktligt accessa ett dött objekt. Är det så prestandakritiskt att du inte vill betala det priset finns get() så är det precis lika snabbt och (o)säkert som din kod med raw pointers.
Alltså denna debatt om pekare som helt flippat ur senaste åren.... Precis som det skulle vara svårt med new och delete.
Du skulle se kod från 90-talet och windows 3.1, då kunde man ha olika typer av pekare
Exempel
LPSTR myString = "Hello, world!";
LP stod då för "long pointer". Innan 32 bitars datorer hade processorer en hel del tricks för sig för att kunna hantera mer minne än 64K (16 bitars dator).
Det har gjorts väldigt mycket program i C och C++ som är stabila.
Problemet är inte pekare utan det verkliga problemet är arkitektur, utvecklare idag har svårt att designa applikationer och rör ihop det. Vem som äger datat är inte alltid tydligt i koden. Svårt att följa
Då skyller man på att pekare är svårt när det egentligen handlar om att designa kod.
Här är en text om deklarativ och imperativ (poängen med varför skriva deklarativ kod)
Imperative vs declarative
Den texten hittar på nya betydelser av orden utan någon bra anledning. Åker i papperskorgen där skräp hör hemma.
Den texten hittar på nya betydelser av orden utan någon bra anledning. Åker i papperskorgen där skräp hör hemma.
Så vad är poängen med att skriva deklarativ kod enligt dig, varför skall man göra det
Stort problem bland programmerare idag är att det saknas ingenjörer, de flesta är teoretiker. De läser saker men förstår inte hur sakerna skall användas
Det "kostar" att kontrollera minne om du inte behöver det.
Allt beror på hur det används och om minne används internt så varför skall det kontrolleras?
Är helt med på att man tar en kostnad för att validera data, men är inte riktigt med på vad du säger här.
Du skriver ju ovan att din "lösning" på problemet är att ha kvar assert() även i release-builds. Det är ju en kostnad även där!
Tar vi programmet du postade, på min dator ihop med g++-14 är ett bygge med optimeringar samt borttagna assert() ca 90 % snabbare jämfört med optimeringar där man har kvar asserts.
Och vad menar du med att "används internt"? I detta fall är det möjligt, även med asserts kvar, att man råkar skicka in indata till din funktion som resulterar i att man både läser och framförallt skriver på ställen där man inte ska skriva. Det är exakt den typen av buggar som ger säkerhetsproblem.
Slängde ihop ett case i ett "minnessäkert" språk, Rust i detta fall, som gör samma grundläggade checkar som din kod, fast där säkerhetsproblem inte är kvar. Precis som din finns det en del icke-korrekta syntax av UUID som kommer accepteras (alla som totalt har 32 hex-digits och där totallängden är 32, 36 eller 38 kommer accepteras, finns flera felaktiga format som passar det).
Rust versionen är kortare, saknar säkerhetsproblem och är dubbelt så snabb som din C++ version med asserts och ca 6 % snabbare än den utan asserts. Och då gav jag ändå C++-versionen fördelen att bara hantera ASCII-strängar medan Rust-versionen hanterar både ASCII och Unicode
pub fn parse_uuid(uuid: &str) -> Option<u128> {
if uuid.len() != 32 && uuid.len() != 36 && uuid.len() != 38 {
return None;
}
let (value, num_nibbles) = uuid
.chars()
.filter_map(char_to_nibble)
.enumerate()
.fold((0, 0), |(acc, _), (index, nibble)| {
(acc + ((nibble as u128) << ((31 - index) * 4)), index + 1)
});
if num_nibbles == 32 {
Some(value)
} else {
None
}
}
fn char_to_nibble(ch: char) -> Option<u8> {
if ch.is_ascii_hexdigit() {
let d = ch as u8;
let x = d & 0x40;
Some((d & 0xf) + (x >> 3) + (x >> 6))
} else {
None
}
}
Ur alla praktiska hänseenden är det möjligt att skriva lika snabba program C, C++ och Rust. Den sista har också fördelen att en lång rad vanliga programmeringsmisstag som potentiellt ger otrevliga buggar i C och C++ går inte att kompilera i Rust. DET är en av orsaken till varför det finns folk som är så entusiastiska Rust användare, det är också precis dessa egenskapar "Safe C++" vill få in i standard C++.
Det skrivet: var initialt också super-entusiastisk till Rust, men det har falnat en hel del då man inser hur extremt komplex språket är (och då kommer jag ändå från C++, var i det jag egentligen lärde mig programmera i början av 90-talet, och C som jag använt väldigt mycket som embedded/kernel-programmerare under mer än 15 år).
C, C++ och Rust har och kommer nog fortsätta ha sin plats framåt. Men majoriteten av ny programvara kommer, och bör, skrivas i andra betydligt enklare miljöer.
precis så här. uppmuntrar slappa lata och ibland inkompetenta utvecklare, tror du verkligen att den resulterade koden blir gedigen med denna approach? skill issue att få minnes problem för att man inte använder smarta pekare dessutom, sånt sitter i ryggmärgen när man kodat ett tag.
låter denna kommentar sätta punkt på denna diskussionen för mig. vill du vara lat så kör hårt själv föredrar jag att göra saker ordentligt. enda sättet att få robust kod påriktigt..
Hmm, fast vi kanske kan vara överens om att Bjarne i alla fall har lite koll på C++?
Han är ju en av de som, i min mening nästan lite naivt, pekar på att C++ skulle inte alls ha så mycket problem om folk slutande skriva "C++ från 90-talet" och istället börja skriva "modern C++" (där modern i praktiken betyder, C++11 och senare).
Andra, som Herb Sutter, har i min mening en mer pragmatisk syn på det hela. Han verkar mer vara inne på att man kommer få leva med gamla synder och att de fortsätter ge problem. För att riktigt göra en "modern bra C++" är han mer inne på att skapa ett språk ovanpå C++ som är enklare att förstå och göra rätt i, detta är sedan helt kompatibelt med C++ likt hur TypeScript är kompatibelt med JS. Carbon är ett exempel, Cppfront är ett annat.
Finns också fall där automatiskt minneshantering i form av tracing-GC kan ge bättre prestanda jämfört med manuel minneshantering. Typexemplet är vid implementation av wait/lock-free algoritmer där en tracing-GC bl.a. i de flesta fall eliminerar ABA-problemet. Det man i praktiken får göra i C och C++ är att implementera "tillräckligt mycket av en GC för att lösa det specifika problem som uppstår".
Rätt använt kan GC också ge flera av de prestanda-fördelar man får med en explicit hanterad areana-allokator. I praktiken lär, rätt använd, en arena-allokator vara något snabbare. Men det är såå mycket enklare att använda GC + den är långt mer flexibel och kan använda i betydligt fler fall.
Så vad är poängen med att skriva deklarativ kod enligt dig, varför skall man göra det
Stort problem bland programmerare idag är att det saknas ingenjörer, de flesta är teoretiker. De läser saker men förstår inte hur sakerna skall användas
Vi kanske kan vara överens om att grafikkort är rätt snabba idag och de kräver att man både förstår HW och domänen för att det ska bli riktigt bra.
De använder rätt mycket ett deklarativt språk. Även om shaders är en variant av C++ (i fallet CUDA och Metal är de bokstavligen en variant av C++, medan HLSL och GLSL är mer åt C hållet) så har de egenskapar från deklarativ programmering.
Varje "typ" av shader har en specifik plats och en relativt snäv uppgift. Så det man skriver är rätt mycket "hur tar jag en vertex från world-space/model-space till device-space?" och "den här positionen hamnade innanför en triangel och innanför klippning, hur ska den färgläggas etc".
Var sak har sin plats kanske?
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Är helt med på att man tar en kostnad för att validera data, men är inte riktigt med på vad du säger här.
Du skriver ju ovan att din "lösning" på problemet är att ha kvar assert() även i release-builds. Det är ju en kostnad även där!
NEJ NEJ NEJ!!!
Var har jag skrivit text som kan tolkas så?
Om jag uttryckt mig så illa så har jag verkligen inte menat det
Observera att vi skriver i en tråd om en mycket komplext ämne och det är hel del som inte går att beskriva på grund av det tar för mycket tid eller svårt att beskriva i text.
NEJ NEJ NEJ!!!
Var har jag skrivit text som kan tolkas så?
Om jag uttryckt mig så illa så har jag verkligen inte menat det
Observera att vi skriver i en tråd om en mycket komplext ämne och det är hel del som inte går att beskriva på grund av det tar för mycket tid eller svårt att beskriva i text.
OK, men utan asserts har ju det exemplet en lång rad säkerhetsproblem. Hoppas det inte är något som används i en "riktig" produkt...
Och då uppstår frågan: hur hanterar du validering av indata i "verkligheten" då?
För här är ju en av de väldigt vassa hörnen i C och C++, gör detta fel och man orsakar rätt lätt ett säkerhetshål (ca 70 % av alla säkerhetshål hos Google, Microsoft och Meta härrör från detta).
Gör man samma misstag i ett "minnessäkert" språk är det fortfarande en bug, men istället för att systemet blir pwned blir det oftast istället "bara" en DoS-vektor.
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Vi kanske kan vara överens om att grafikkort är rätt snabba idag och de kräver att man både förstår HW och domänen för att det ska bli riktigt bra.
De använder rätt mycket ett deklarativt språk. Även om shaders är en variant av C++ (i fallet CUDA och Metal är de bokstavligen en variant av C++, medan HLSL och GLSL är mer åt C hållet) så har de egenskapar från deklarativ programmering.
Varje "typ" av shader har en specifik plats och en relativt snäv uppgift. Så det man skriver är rätt mycket "hur tar jag en vertex från world-space/model-space till device-space?" och "den här positionen hamnade innanför en triangel och innanför klippning, hur ska den färgläggas etc".
Var sak har sin plats kanske?
Att skriva generell imperativ kod har alltid ett syfte och det är att göra det så lätt som möjligt att lösa problem med olika domäner. Hur långt detta går att göra beror helt på hur skickliga programmerare är för det är just detta som tar tid att lära sig.
Beroende på hur långt det går att lösa så måste man ha med imperativ kod i deklarativa lösningar. Till och med databaser, där kan du skriva lagrade procedurer som har en del imperativ kod.
Det är ok att blanda deklarativ kod med imperativ kod men målet är alltid att ha så lite imperativ kod som möjligt.
Generell kod däremot, där är målet att underlätta och göra den deklarativa biten så enkel som möjligt. Desto enklare det går att göra den deklarativa biten desto mer möjligheter att göra bra lösningar utan att anställa duktiga programmerare som OCKSÅ måste läsa på domänen.
Du tar upp "spelprogrammerare", fundera över varför det finns något som heter "spelprogrammerare". De programmerar väl precis som andra programmerare?
Det kallas för spelprogrammerare för det är ett yrke där personer dels kan programmera men också lärt sig domänen "grafik".
Det finns gott om "motorer" skrivna för att underlätta grafiken men det går inte att förenkla så mycket så att en person helt utan programmeringskunskaper kan skapa grafik för spel.
Att koda grafik är inte en 100% deklarativ lösning utan det är deklarativ kod blandat med imperativ kod. Målet för dessa programmerare är att skriva så lite imperativ kod som möjligt eftersom den typen av kod är hårdkodad.
När företag måste hitta utvecklare som behöver vara duktiga på något utöver själva programmeringen, då får de ofta sänka nivån på kodkvalitet. Orsaken är att urvalet genast begränsas. Människor kan inte bli experter i allt
OK, men utan asserts har ju det exemplet en lång rad säkerhetsproblem. Hoppas det inte är något som används i en "riktig" produkt...
Och då uppstår frågan: hur hanterar du validering av indata i "verkligheten" då?
För här är ju en av de väldigt vassa hörnen i C och C++, gör detta fel och man orsakar rätt lätt ett säkerhetshål (ca 70 % av alla säkerhetshål hos Google, Microsoft och Meta härrör från detta).
Gör man samma misstag i ett "minnessäkert" språk är det fortfarande en bug, men istället för att systemet blir pwned blir det oftast istället "bara" en DoS-vektor.
Tror jag skrev tidigare att svårigheter med minne inte handlar om att det är svårt att hantera minne utan arkitektur.
Att förstå "new" och "delete" är inte svårt. Det svåra är när det blir tusentals olika minnesblock där man måste hålla reda på alla dessa.
Om du tittar på metodnamnet i koden för nu kommer vi in på ett helt annat område och det är hur man skriver kod.
uuid::read_s
Varför avslutas metoden med _s ?
Hur utvecklare väljer att skriva sin kod varierar men detta är en stil jag brukar använda och med _s vet jag att det är en "free function" (i detta fallet en statisk medlemsmetod.)
Rekommenderar den här videon - CppCon 2017: Klaus Iglberger “Free Your Functions!”.
Det här är en typ av metod som aldrig anropas från deklarativ kod i produktionskod. Det är mer av en teknik för att underlätta att skriva imperativ kod och det är enkelt att labba med den.
Ibland när jag skriver objekt så har objektet metoder som bara wrappar statiska metoder i klassen, beror lite på vad som görs självklart.
Och det är ett enkelt mönster där jag också vet att minne och annat måste vara kontrollerat innan dessa anropas
Går säkert att se i resten av kod jag klistrat in att jag praktiserar "hungarian notation"
Passar på och klistra in mer kod för att exemplifiera free functions i object och en teknik i att producera kod som är effektiv. För mig kan free functions vara slarviga för de används för att det är bl.a. lätt att refaktorera koden då de inte är tänkta att anropas från produktionskod.
Kan vara så att man vill skapa någon funktionalitet och börjar labba fram koden genom att skriva test. Enklast då att skriva olika metoder. När koden känns bra wrappas dessa med en klass och snyggas till. De statiska medlemsmeoderna anropas aldrig utifrån och det är lättare att låta bli det med om de avslutas med _s för det gör att koden ser fullare ut.
/// ## argument methods
/// return param the position points to
static argument get_argument_s(const_pointer pPosition);
/// return editable param based on position
static argument_edit get_edit_param_s(arguments* parguments, pointer pPosition);
/// count internal param length in bytes
static unsigned int get_total_param_length_s(const_pointer pPosition);
static unsigned int get_total_param_length_s(std::string_view stringName, const argument argumentValue);
static std::vector<argument> get_argument_all_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName);
static std::vector<gd::variant_view> get_argument_all_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName, tag_view);
static std::vector<gd::variant_view> get_argument_section_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName, tag_view);
/// ## move methods
/// move pointer to next value in buffer
static pointer next_s(pointer pPosition);
static const_pointer next_s(const_pointer pPosition);
static const_pointer next_s(const_pointer pPosition, unsigned uSecondIndex, const_pointer pEnd );
static pointer next_s(pointer pPosition, unsigned uSecondIndex, const_pointer pEnd );
/// ## Calculate size in bytes needed for argument values stored in arguments object
static unsigned int sizeof_s(const argument& argumentValue);
static unsigned int sizeof_s(const gd::variant_view& VV_, tag_view);
static unsigned int sizeof_s( const std::string_view& stringName, const gd::variant_view& VV_, tag_view );
static unsigned int sizeof_s(uint32_t uNameLength, param_type uType, unsigned int uLength);
static inline unsigned int sizeof_name_s(uint32_t uNameLength) { return uNameLength + 2; }
Det skrivet: var initialt också super-entusiastisk till Rust, men det har falnat en hel del då man inser hur extremt komplex språket är (och då kommer jag ändå från C++, var i det jag egentligen lärde mig programmera i början av 90-talet, och C som jag använt väldigt mycket som embedded/kernel-programmerare under mer än 15 år).
C, C++ och Rust har och kommer nog fortsätta ha sin plats framåt. Men majoriteten av ny programvara kommer, och bör, skrivas i andra betydligt enklare miljöer.
Jag tror också att RUST kommer öka med om man nu skall tävla mellan språk vilket jag nog kan tycka är lite löjligt, alltså detta med "vilket språk är bäst"-leken.
C++ har en hel del fördelar som andra språk missar så även om C++ aldrig blir ett språk för majoriteten av utecklare kommer en majoritet av den maskinkod som körs på datorer komma från kompilerad C eller C++.
Förklaring:
Det är mycket viktigt att lära sig förstå hur man hanterar minne för att förstå hur man producerar bra kod. Det här området måste programmerare träna på om de vill bli bättre på prestandakritisk eller generell funktionalitet.
Språk som tar bort möjligheter att träna på att jobba med minne gör också att utvecklare inte tränar på det.
En majoritet av processorns jobb handlar om att läsa och skriva till minne, det är dennes uppgift. Att inte lära sig hantera vad en processor spenderar en majoritet av sin tid med, det fungerar inte.
De som lärt sig hantera minne och arkitektur kan ofta snabbt se problemen när andra utvecklare som inte lärt sig det här och hur snabbt de trasslar in sig. Ta C# eller Java. Så länge dessa utvecklare följer ramverk brukar det fungera. Men så fort de skall lösa något eget där det inte finns färdiga ramverk att följa blir det kaos direkt. Orsaken är att C# och Java inte är språk som gör att utvecklare tränar på den absolut viktigaste uppgiften för en CPU, att hantera minnet.
Minne är svårt när det blir så extremt mycket men därför måste man också träna på det.
RUST kommer därför mest handla om mindre projekt då de som utvecklar i språket aldrig tränar på vad de måste träna på för att kunna skala upp koden. RUST är säkert jättekul för många som tycker minneshantering bara känns som en mardröm. Men allt är inte kul.
Hmm, fast vi kanske kan vara överens om att Bjarne i alla fall har lite koll på C++?
Han är ju en av de som, i min mening nästan lite naivt, pekar på att C++ skulle inte alls ha så mycket problem om folk slutande skriva "C++ från 90-talet" och istället börja skriva "modern C++" (där modern i praktiken betyder, C++11 och senare).
Ja Bjarne är superduktig på C++ språket självklart. Men det är inte samma sak som att han är duktig på att skriva kod, hur man skriver stora mängder kod för att skapa applikationer som fungerar för användare. Jag tror inte det är hans intresse heller, hans fokus ligger på C++ språket.
precis så här. uppmuntrar slappa lata och ibland inkompetenta utvecklare, tror du verkligen att den resulterade koden blir gedigen med denna approach? skill issue att få minnes problem för att man inte använder smarta pekare dessutom, sånt sitter i ryggmärgen när man kodat ett tag.
låter denna kommentar sätta punkt på denna diskussionen för mig. vill du vara lat så kör hårt själv föredrar jag att göra saker ordentligt. enda sättet att få robust kod påriktigt..
Nu blir jag besviken, har du sagt A får du väl lov att säga B också? Jag väntar fortfarande på att få veta vilka negativa effekter smarta pekare har på kodkvalité. Nu vill jag veta hur orsakssambanden ser ut. Är det så att man använder smarta pekare om man är slö, lat och inkompetent eller är det så man blir slö, lat och inkompetent av att använda smarta pekare?
Jag håller med om att kompetens hos utvecklarna är ett problem. Men beaktande att vi har horder med medelbegåvade programmerare, på vilket sätt skulle världen bli bättre om de inte hade smarta pekare till hjälp? Skulle de automatiskt bli bättre programmerare om du tog bort stödhjulen eller skulle vi bara få ännu längre utvecklingstid och ännu fler buggar i släppt programvara? På vilket sätt blir programvara mer robust av att man, som du, envisas med att skriva boilerplate-kod själv? Tar det inte bara längre tid och introducerar fler buggar? Bibliotekskoden brukar vara ganska välskriven och vältestad.
Jag har programmerat yrkesverksamt i C och C++ sedan examen från KTH 1990, så jag tror jag kvalar in i kategorin "kodat ett tag". Ja, jo, new och delete, precis som malloc och free, sitter nog i ryggmärgen, men jag tar tacksamt emot allting som gör att jag slipper skriva kod. Du får tycka att jag är lat, själv tycker jag att det är smart. Jag ägnar mina hjärncykler till att lösa det verkliga problemet, snarare än att lösa problem som programspråket skulle hantera åt dig om du bara tog ett steg in i framtiden, om nu C++11 kan ses som framtiden
Nu blir jag besviken, har du sagt A får du väl lov att säga B också? Jag väntar fortfarande på att få veta vilka negativa effekter smarta pekare har på kodkvalité. Nu vill jag veta hur orsakssambanden ser ut. Är det så att man använder smarta pekare om man är slö, lat och inkompetent eller är det så man blir slö, lat och inkompetent av att använda smarta pekare?
Jag håller med om att kompetens hos utvecklarna är ett problem. Men beaktande att vi har horder med medelbegåvade programmerare, på vilket sätt skulle världen bli bättre om de inte hade smarta pekare till hjälp? Skulle de automatiskt bli bättre programmerare om du tog bort stödhjulen eller skulle vi bara få ännu längre utvecklingstid och ännu fler buggar i släppt programvara? På vilket sätt blir programvara mer robust av att man, som du, envisas med att skriva boilerplate-kod själv? Tar det inte bara längre tid och introducerar fler buggar? Bibliotekskoden brukar vara ganska välskriven och vältestad.
Jag har programmerat yrkesverksamt i C och C++ sedan examen från KTH 1990, så jag tror jag kvalar in i kategorin "kodat ett tag". Ja, jo, new och delete, precis som malloc och free, sitter nog i ryggmärgen, men jag tar tacksamt emot allting som gör att jag slipper skriva kod. Du får tycka att jag är lat, själv tycker jag att det är smart. Jag ägnar mina hjärncykler till att lösa det verkliga problemet, snarare än att lösa problem som programspråket skulle hantera åt dig om du bara tog ett steg in i framtiden, om nu C++11 kan ses som framtiden
Vet inte vad du förväntar dig att jag ska svara på sådant här dravel? Själv kodar jag en blandning av C++17/20.
Nu blir jag besviken, har du sagt A får du väl lov att säga B också? Jag väntar fortfarande på att få veta vilka negativa effekter smarta pekare har på kodkvalité.
Får jag ge ett svar
Programmerare låter bli att träna på att själva sköta minnet och tar därmed bort ett viktigt moment för att skriva kvalitetsmässigt bra kod.
Jag tror de flesta som inte tycker minne är speciellt svårt uppfattar det som extra bök. Ungefär samma som alla dessa casttningar som finns i C++. Tänkte på static_cast, const_cast, reinterpret_cast och dynamic_cast
Det händer att jag använder smarta pekare men då oftast i test
exempel
{
using namespace gd::argument::shared;
std::unique_ptr<const char, decltype([](auto p_){ std::cout << p_ << std::endl; } )> quit_("\n## End section - get vector for name values");
gd::argument::shared::arguments arguments_;
arguments_.append_argument( "values", 0, arguments::tag_view{});
arguments_.append_many( 100, 200, 300, 400, 500 );
arguments_.append_argument( "sum", 0u, arguments::tag_view{} );
arguments_.append_argument("names", "name value", arguments::tag_view{});
arguments_.append_many("100 as text", "200 as text", "300 as text");
auto vector_ = arguments_.get_argument_section( "values", arguments::tag_view{} );
std::cout << gd::debug::print( vector_ ) << "\n";
vector_ = arguments_.get_argument_section( "names", arguments::tag_view{} );
std::cout << gd::debug::print( vector_ ) << "\n";
}
Vet inte vad du förväntar dig att jag ska svara på sådant här dravel?
Jag förväntar mig att du skall svara på mina frågor istället för att övergå till name calling. När jag ifrågasätter din ståndpunkt och ställer frågor om varför du tycker som du tycker så undviker du att svara på det jag frågar om och repeterar "slö, lat och inkompetent".
Du skrev:
Jag förstår syftet med unique_ptr's, jag gillar det bara inte. Det uppmuntrar amatörmässig kod, och har således en kontraproduktiv effekt.
Allt som hanterar minne i C och C++ arbetar med "råa pekare" på något lager. Allt ovanpå är helt enkelt extra komplexitet. Ibland är den komplexiteten en kostnad för funktionalitet som är befogad och bra, men i detta fallet är det en kostnad som kodmässigt inte tillför någon funktionalitet. Det tillför dock som du säger något till programmerare som väljer att vara lata och inkompetenta, men det är viktigt att komma ihåg att det har en kostnad för den resulterande kodkvaliteten också.
Jag frågade:
Varför är det amatörmässigt att nyttja finesserna som finns i språket?
Det är ju hela poängen med smart pointers, de beter sig som vanliga pekare men jag slipper komma ihåg att göra saker och jag slipper minnesfel. Hur kan detta vara dåligt?
På vilket sätt skulle du säga att smarta pekare "har en kostnad för den resulterande kodkvaliteten"? Färre minnesfel och enklare kod skulle jag säga förbättrar kodkvaliteten.
Du citerade "men jag slipper komma ihåg att göra saker och jag slipper minnesfel" och skrev följande svar:
precis så här. uppmuntrar slappa lata och ibland inkompetenta utvecklare, tror du verkligen att den resulterade koden blir gedigen med denna approach? skill issue att få minnes problem för att man inte använder smarta pekare dessutom, sånt sitter i ryggmärgen när man kodat ett tag.
Jag frågar:
Men beaktande att vi har horder med medelbegåvade programmerare, på vilket sätt skulle världen bli bättre om de inte hade smarta pekare till hjälp? Skulle de automatiskt bli bättre programmerare om du tog bort stödhjulen eller skulle vi bara få ännu längre utvecklingstid och ännu fler buggar i släppt programvara?
På vilket sätt blir programvara mer robust av att man, som du, envisas med att skriva boilerplate-kod själv? Tar det inte bara längre tid och introducerar fler buggar? Bibliotekskoden brukar vara ganska välskriven och vältestad.
Du svarar:
Vet inte vad du förväntar dig att jag ska svara på sådant här dravel? Själv kodar jag en blandning av C++17/20.
Du har ju hittills inte ens försökt ge en motivation till varför smart pointers skulle ge sämre kod. Ej heller den intressantare frågan om varför koden skulle bli bättre om C++ inte hade smart pointers, för om smart pointers get dålig kod, då måste väl borttagandet av smart pointers ge bättre kod?
Får jag ge ett svar
Programmerare låter bli att träna på att själva sköta minnet och tar därmed bort ett viktigt moment för att skriva kvalitetsmässigt bra kod.
Skriver du idiomatisk modern C++-kod behöver du inte kunna hantera minnet själv. Standardbiblioteket sköter det åt dig! Det går sedan 14 år alldeles utmärkt att skriva C++-program med dynamiskt minne utan att använda new och delete. På vilket sätt blir det bättre kodkvalité om man envisas med att själv hantera minnet med new och delete?
Jag håller med om att kompetens hos utvecklarna är ett problem. Men beaktande att vi har horder med medelbegåvade programmerare, på vilket sätt skulle världen bli bättre om de inte hade smarta pekare till hjälp? Skulle de automatiskt bli bättre programmerare om du tog bort stödhjulen eller skulle vi bara få ännu längre utvecklingstid och ännu fler buggar i släppt programvara? På vilket sätt blir programvara mer robust av att man, som du, envisas med att skriva boilerplate-kod själv? Tar det inte bara längre tid och introducerar fler buggar? Bibliotekskoden brukar vara ganska välskriven och vältestad.
Har du sett C++ när en programmerare försöker använda stl till nästan allt, den koden är inte rolig att jobba med.
Det finns massa trick att ta till om man själv hanterar minnet och få ner mängden allokeringar som en kompilator aldrig kan lista ut eller optimera. Och skrivs egna objekt brukar det räcka med att radera minnet i destructorn så är det klart.
Om jag använder smartpointers är det nästan enbart för något lokalt objekt i en metod som har flera olika möjligheter att avsluta eller har en del felkontroll för då hjälper det självklart (blir mindre kod).
De gånger där det potentiellt skulle kunna vara bra att använda en smartpointer är det oftast bättre med andra objekt i stl, exmpelvis std::vector.
Skriver du idiomatisk modern C++-kod behöver du inte kunna hantera minnet själv. Standardbiblioteket sköter det åt dig! Det går sedan 14 år alldeles utmärkt att skriva C++-program med dynamiskt minne utan att använda new och delete. På vilket sätt blir det bättre kodkvalité om man envisas med att själv hantera minnet med new och delete?
Ta exemplet med att skriva en strängklass som endast får ha en enda allokering. förutom buffern behöver du för att ha snabbhet i klassen, dess aktuella längd, hur stor buffern är innan den behöver allokera mer minne samt pekare till själva strängen.
Två stycken variabler och en buffer. Hur kan man lagra allt detta i ett enda block?
Du kan göra ett block som i början också lagrar aktuell längd och total längd på buffer
[ [sträng längd] [total buffer längd] [sträng.....] ]
När strängen allokeras som blir total mängd minne som allokeras sizeof(uint64_t) + sizeof(uint64_t) + "total buffer längd"
Skall du blanda in smartpointers så blir sådant här inte lättare.
Det går såklart inte att använda en std::vector<char> eftersom STL e dålitt
Skriver du idiomatisk modern C++-kod behöver du inte kunna hantera minnet själv. Standardbiblioteket sköter det åt dig! Det går sedan 14 år alldeles utmärkt att skriva C++-program med dynamiskt minne utan att använda new och delete. På vilket sätt blir det bättre kodkvalité om man envisas med att själv hantera minnet med new och delete?
Vill du faktiskt dra nytta av att du använder ett native språk så _vill du_ hantera minnet själv. Du vill kunna göra det för att hantera minneslokalitet, undvika onödigt frekventa eller små allokeringar, optimera minnesaccess för cache coherency/locality och så vidare. Det finns ett väldigt brett spann på kodkvalitet när det kommer till C/C++, och du kommer _aldrig_ att uppnå dom högsta nivåerna om du är lat med din minneshantering. Du _VILL_ veta precis hur var och när ditt minne allokeras, läses, skrivs, fetchas till cache på olika trådar och så vidare. Bra eller dåligt skriven kod här kan vara skillnaden på flera storleksordningar i exekveringstider/prestanda.
Om du inte gör det kan du lika gärna använda C# eller ett annat högnivå-språk med GC, runtime/AOT/JIT osv. istället. Men oavsett blir du ju också kvar med usel prestanda i jämförelse som resultat.
Tror jag skrev tidigare att svårigheter med minne inte handlar om att det är svårt att hantera minne utan arkitektur.
Att förstå "new" och "delete" är inte svårt. Det svåra är när det blir tusentals olika minnesblock där man måste hålla reda på alla dessa.
Om du tittar på metodnamnet i koden för nu kommer vi in på ett helt annat område och det är hur man skriver kod.
uuid::read_s
Varför avslutas metoden med _s ?
Hur utvecklare väljer att skriva sin kod varierar men detta är en stil jag brukar använda och med _s vet jag att det är en "free function" (i detta fallet en statisk medlemsmetod.)
Rekommenderar den här videon - CppCon 2017: Klaus Iglberger “Free Your Functions!”.
Det här är en typ av metod som aldrig anropas från deklarativ kod i produktionskod. Det är mer av en teknik för att underlätta att skriva imperativ kod och det är enkelt att labba med den.
Ibland när jag skriver objekt så har objektet metoder som bara wrappar statiska metoder i klassen, beror lite på vad som görs självklart.
Och det är ett enkelt mönster där jag också vet att minne och annat måste vara kontrollerat innan dessa anropas
Går säkert att se i resten av kod jag klistrat in att jag praktiserar "hungarian notation"
Passar på och klistra in mer kod för att exemplifiera free functions i object och en teknik i att producera kod som är effektiv. För mig kan free functions vara slarviga för de används för att det är bl.a. lätt att refaktorera koden då de inte är tänkta att anropas från produktionskod.
Kan vara så att man vill skapa någon funktionalitet och börjar labba fram koden genom att skriva test. Enklast då att skriva olika metoder. När koden känns bra wrappas dessa med en klass och snyggas till. De statiska medlemsmeoderna anropas aldrig utifrån och det är lättare att låta bli det med om de avslutas med _s för det gör att koden ser fullare ut.
/// ## argument methods
/// return param the position points to
static argument get_argument_s(const_pointer pPosition);
/// return editable param based on position
static argument_edit get_edit_param_s(arguments* parguments, pointer pPosition);
/// count internal param length in bytes
static unsigned int get_total_param_length_s(const_pointer pPosition);
static unsigned int get_total_param_length_s(std::string_view stringName, const argument argumentValue);
static std::vector<argument> get_argument_all_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName);
static std::vector<gd::variant_view> get_argument_all_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName, tag_view);
static std::vector<gd::variant_view> get_argument_section_s(const_pointer pBegin, const_pointer pEnd, std::string_view stringName, tag_view);
/// ## move methods
/// move pointer to next value in buffer
static pointer next_s(pointer pPosition);
static const_pointer next_s(const_pointer pPosition);
static const_pointer next_s(const_pointer pPosition, unsigned uSecondIndex, const_pointer pEnd );
static pointer next_s(pointer pPosition, unsigned uSecondIndex, const_pointer pEnd );
/// ## Calculate size in bytes needed for argument values stored in arguments object
static unsigned int sizeof_s(const argument& argumentValue);
static unsigned int sizeof_s(const gd::variant_view& VV_, tag_view);
static unsigned int sizeof_s( const std::string_view& stringName, const gd::variant_view& VV_, tag_view );
static unsigned int sizeof_s(uint32_t uNameLength, param_type uType, unsigned int uLength);
static inline unsigned int sizeof_name_s(uint32_t uNameLength) { return uNameLength + 2; }
Just a FYI: för C-programmerare lär första tanken kring vad ett _s suffix betyder inte matcha med vad som står här (där står det för "safe").
Vilket leder tillbaka till frågan: varför anser du det irrelevant att det finns klara minnes-buggar i den kod du listade? Om de är irrelevanta, varför finns det då ens assert() som fångar de flesta?
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Ta exemplet med att skriva en strängklass som endast får ha en enda allokering. förutom buffern behöver du för att ha snabbhet i klassen, dess aktuella längd, hur stor buffern är innan den behöver allokera mer minne samt pekare till själva strängen.
Två stycken variabler och en buffer. Hur kan man lagra allt detta i ett enda block?
Du kan göra ett block som i början också lagrar aktuell längd och total längd på buffer
[ [sträng längd] [total buffer längd] [sträng.....] ]
När strängen allokeras som blir total mängd minne som allokeras sizeof(uint64_t) + sizeof(uint64_t) + "total buffer längd"
Skall du blanda in smartpointers så blir sådant här inte lättare.
Tror du grovt underskattar hur mycket de som designar standardbiblioteken faktiskt tänker och förstår hur moderna CPUer fungerar och hur t.ex. strängar används i prestandakritiska program.
Din representation ovan kanske verkar smart. Det är fullt rimligt, men den är i praktiken långsammare än den som i alla fall LLVM libc++ och GNU C++ biblioteket använder.
För små strängar (20-25 tecken) så sparar båda dessa hela strängen direkt på stacken om man gör detta
std::string s = "Hello World!";
Så ingen minnesallokering alls, och då stacken i praktiken inte bara ligger i L1D$ utan också i praktiken också har ett giltigt entry i TLB-L1$ så är det supereffektivt att läsa/skriva.
För större strängar är det oftast mer effektivt att låta kapacitet och längd ligga i den del av strängen som hålls "by-value" (och då ligger på stacken för automatiska variabler), detta då väldigt vanliga operationer som att kolla längden på strängen då blir snabbare än att ha ett enda block på heap.
TL;DR se till att använd standardbiblioteket i C++, det är designat av folk som faktiskt vet vad de håller på med (i alla fall för de mest populära systemen/kompilatorerna)!!!
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Så vad är poängen med att skriva deklarativ kod enligt dig, varför skall man göra det
Det är ingen särskild poäng med att skriva deklarativ kod, precis som det inte finns någon särskild poäng med att skriva imperativ kod. Det är bara lite olika angreppssätt, och olika sätt att skriva kod.
Vissa problem är enklare att lösa med det ena sättet, andra problem med det andra - men det har ytterst sällan eller aldrig att göra med om problemet är domänspecifikt eller inte.
Det är ungefär som att fråga vad det är för poäng med att använda franska istället för tyska. Man kan ju säga samma saker i bägge språken.
- 92 procent aktiverar DLSS och RTX 50-serien säljer som smör114
- Äntligen fått jobb inom mjukvaruutveckling, hur går det för er?42
- Tips: Frame Generation für alle! "Lossless Scaling"12
- Nytt skydd mot bluffsamtal tvingar bedragarna att betala47
- Half-Life 2 RTX-demo släpps den 18 mars27
- Qled TV TCL sprucken inifrån3
- 9070 / XT - Owners club211
- Vilket mobilabonnemang har ni och vad betalar ni?72
- Har jag köpt rätt nätaggregat (Corsair RM850e)?6
- RTX 5070 Ti - Owners club23
- Köpes Köpes 7800x3d/9800x3d
- Köpes Köper Iphone X
- Säljes 2080 + i9 9900k
- Säljes HP ProDesk 600 G2 SFF & Surface Pro 3
- Bytes PALIT RTX 5090 GAMEROCK
- Säljes ASUS GeForce RTX 4090 24GB TUF Gaming OC
- Säljes Uppgraderingspaket
- Säljes AMD Ryzen 7600X + ASUS ProArt B650-Creator
- Säljes Gigabyte Geforece RTX 4080 Super
- Säljes Razer blackwidow V3
- Så vill Elgiganten locka över de datorintresserade47
- Half-Life 2 RTX-demo släpps den 18 mars27
- Quiz: AMD vs Intel – Vad kan du om processor-jättarna?98
- 92 procent aktiverar DLSS och RTX 50-serien säljer som smör114
- Intel utser industriveteran till ny vd – vill skapa "nytt Intel"15
- Här är datorn som drivs av hjärnceller37
- Microsoft täpper allvarlig säkerhetsbrist i Windows10
- Stabil tillgång på AMD Ryzen 9 9950X3D vid säljstart55
- Nvidia kan visa upp Geforce RTX 5060 imorgon32
- Veckans fråga: Hur mycket mobildata använder du?81
Externa nyheter
Spelnyheter från FZ