mboost-dp1

Bjarne Stroustrup’s Plan for Bringing Safety to C++


Gå til bund
Gravatar #2 - larsp
30. okt. 2023 06:13
Stroustrup has arrived at his solution: profiles. (...) “I think profile annotations should help with that problem.”

export My_module[[provide(memory_safety)]];
import std [[enable(memory_safety)]];
import Mod [suppress(type_safety)]];

Profiler der enabler forskellige typer statisk kodeanalyse, som kan være slået til og fra per modul, lidt som et kludetæppe? C++ kamæleonen får flere farver og et ekstra sæt legemer...

C++ er drevet af Stroustrup's ego og bliver større og mere kompleks hele tiden. For "der kan ikke være andre sprog end C++", som han siger. Nu kommer der så et sæt håndtag der kan slås til og fra per modul, memory_safety, type_safety og concurrency_safety, og det giver 2**3 = 8 forskellige sikkerhedsmodes et modul kan skrives i, og hver mode kan sikkert enables og disables lokalt. Keep it simple, baby. Jeg misunder ikke C++ udviklere.

Elsewhere in the talk Stroustrup also points out that “A lot of the so-called ‘safe’ languages outsource all the low-level stuff to C or C++,” temporarily escaping the original language to access hardware resources or even the operating system (which is often written in C)

Der er da ikke noget galt i at have forskellige sprog i en software stak. Lowlevel og highlevel programmering har vidt forskellige formål og idealer, og det giver mening at have forskellige sprog.
Gravatar #3 - arne_v
30. okt. 2023 13:22
larsp (2) skrev:

Profiler der enabler forskellige typer statisk kodeanalyse, som kan være slået til og fra per modul, lidt som et kludetæppe? C++ kamæleonen får flere farver og et ekstra sæt legemer...

C++ er drevet af Stroustrup's ego og bliver større og mere kompleks hele tiden. For "der kan ikke være andre sprog end C++", som han siger. Nu kommer der så et sæt håndtag der kan slås til og fra per modul, memory_safety, type_safety og concurrency_safety, og det giver 2**3 = 8 forskellige sikkerhedsmodes et modul kan skrives i, og hver mode kan sikkert enables og disables lokalt. Keep it simple, baby. Jeg misunder ikke C++ udviklere.


Der findes to filosofier med hensyn til programmering-sprog:

A) big is beautiful

* mere funktionalitet er bedre end mindre funktionalitet
* et sprog kan godt dække alle behov bare det har nok funktionalitet
* at sproget er svært er ikke en ulempe - det er en fordel, fordi så kan alle de middelmådige udviklere ikke finde ud af at bruge sproget
* hvis nogen ønsker funktionalitet X så bør den tilføjes

B) small is beautiful

* et simpelt sprog er bedre end et kompliceret sprog
* et sprog bør fokusere på nogle få specifikke behov og dem med andre behov må vælge et andet sprog
* det er vigtigt for vedligehold at et sprog er rimeligt nemt at gå til
* funktionalitet X bør kun tilføjes hvis mange ønsker det

C++ er ret meget i kategori A.

(sammen med PL/I, Ada95, Scala etc.)
Gravatar #4 - arne_v
30. okt. 2023 13:52
larsp (2) skrev:

Elsewhere in the talk Stroustrup also points out that “A lot of the so-called ‘safe’ languages outsource all the low-level stuff to C or C++,” temporarily escaping the original language to access hardware resources or even the operating system (which is often written in C)

Der er da ikke noget galt i at have forskellige sprog i en software stak. Lowlevel og highlevel programmering har vidt forskellige formål og idealer, og det giver mening at have forskellige sprog.


Absolut.

Det giver god mening at bruge det rigtige sprog til en given opgave. Og der er ikke nogen grund til automatisk at antage at det sprog der er bedste til at udregne kundernes rabat er det samme sprog som er bedst til at styre de B-tree pages der bruges i kunde-databasen.

Derudover er der en vis bias i kriteriet. De mest brugte styre systemer (Linux, Windows, diverse Unix) har C API (for visse Windows API - C++ API), hvilket gør det praktisk nødvendigt at bruge C eller C++ eller et andet sprog som kan kalde C funktioner til system interface.

Gravatar #5 - larsp
31. okt. 2023 10:25
arne_v (3) skrev:
C++ er ret meget i kategori A.

Jeg tror der er noget macho-ingeniør tænkning over C++ a'la: vores sprog er bedre end alle andres, for vi kan alle paradigmer, har det mest avancerede templating system og vores binaries kører native med optimal performance... og med den hat på forstår man godt at Stroustrup er irriteret over at andre sprog kan noget som hans baby ikke kan, f.eks. compiler garanteret sikkerhed.

Det er iøvrigt pudsigt hvordan OOP har været på tilbagetog de senere år. Ingen forsvarer multiple-inheritance mere, og færre og færre forsvarer nedarvning i det hele taget, som et sundt paradigme. Det nye hotte, som jeg forstår det, er en art funktionel programmering hvor man har data og behandling af data separat. I stedet for at have klasser der væver data og funktioner sammen.

Jeg er nu meget glad for OOP og bruger det selv i Python hele tiden, inklusiv multiple interitance. Der er noget ved den måde det gøres på i Python der gør det ligetil og nemt at forstå. Her er f.eks. en constructor fra min kode:

class GcpMessageMgmtResp(GenericFields, GcpMessageType):
. def __init__(self, return_code:int):
. GcpMessageType.__init__(self, GcpMessage.MESSAGE_ID_MGMT_RESP)
. GenericFields.__init__(self, "Device Management Response")
. self.ReturnCode = return_code

Bemærk, de to superklassers initialisers kaldes separat og er bare funktionskald der lægger ting i objektets dict, når det kommer til stykket. Nemt at forstå, og en IDE kan nemt assistere.

Multiple inheritance i C++ derimod ...
Gravatar #6 - arne_v
31. okt. 2023 13:55
larsp (5) skrev:

Det er iøvrigt pudsigt hvordan OOP har været på tilbagetog de senere år. Ingen forsvarer multiple-inheritance mere, og færre og færre forsvarer nedarvning i det hele taget, som et sundt paradigme.


Implementation inheritance

Det er vel lidt frem og tilbage indtil teknologien blev helt moden.

Sidst i 80erne og først i 90erne var OOP nyt og over-hyped. Opfinderne af OOP havde nok en god forståelse af hvordan det skulle bruges, men de middelmådige og dårlige udviklere gik bare i gang med at arve implementation overalt. Resultatet var ikke kønt.

Sidst i 90erne og i 00erne kom der så en reaktion. James Gosling udtalte at det han fortrød mest i Java var extends keyword. Joshua Bloch i hans meget kendte bog Effective Java populariserede mantraet "Favor composition over inheritance". Og lige pludselig skulle alle undgå implementations arv.

Idag tror jeg at man er landet et fornuftigt sted med hensyn til implementations arv.

Implementations arv er helt fint hvis:
1) det faktisk giver mening i domain (Lisskov "is a")
2) den udvikler der har skrevet den kode som arves var opmærksom på at koden kunne arves

Det første har altid været kendt. Der var bare nogen som ikke forstod det.

Det andet er en erkendelse som er kommet med erfaring. Og jeg ser det som karakteristisk at Java (1995) som default tillader arv fra klasse men kan forbyde det med final, mens Kotlin (2011) som default forbyder arv fra klasse men kan tillade det med open.

Multiple implementation inheritance

Da man begyndte at være bekymret over hvordan implementation inheritance blev brugt var et af de ofte fremhævede problemer multiple implementation inheritance og DoD.

Nye sprog som Java og C# droppede multiple implementation inheritance.

Og så blev man klogere. Problemet var nok ikke multiple implementation inheritance som koncept men den meget fleksible måde som C++ havde implementeret det på.

Og nye sprog listede en form for multiple implementation inheritance ind. Og Java og C# retrofittede.

C++ og Python arver bare.

C++:


#include <iostream>

using namespace std;

class P1
{
public:
void M1()
{
cout << "P1.M1" << endl;
}
};

class P2
{
public:
void M2()
{
cout << "P2.M2" << endl;
}
};

class C : public P1, public P2
{
};

int main()
{
C *o = new C();
o->M1();
o->M2();
delete o;
return 0;
}


Python:


class P1(object):
def m1(self):
print('P1.m1')

class P2(object):
def m2(self):
print('P2.m2')

class C(P1,P2):
pass

o = C()
o.m1()
o.m2()


For nyere sprog skal man gøre noget specielt og der er restriktioner på hvad man kan gøre (hvilket skulle forhindre de værste problemer).

Scala:


trait P1 {
def m1(): Unit = println("P1.m1")
}

trait P2 {
def m2(): Unit = println("P2.m2")
}

class C extends Object with P1 with P2 {
}

object MI {
def main(args: Array[String]): Unit = {
val o = new C()
o.m1()
o.m2()
}
}


Kotlin:


interface P1 {
fun m1() {
println("P1.m1")
}
}

interface P2 {
fun m2() {
println("P2.m2")
}
}

class C : P1, P2 {
}

fun main() {
val o = C()
o.m1()
o.m2()
}


PHP:


<?php
// since pHP 5.4 (2012)

trait P1 {
function m1() {
echo "P1.m1\r\n";
}
}

trait P2 {
function m2() {
echo "P2.m2\r\n";
}
}

class C {
use P1, P2;
}

$o = new C();
$o->m1();
$o->m2();

?>


Java:


// since Java 8 (2014)

public class MI {
public static interface P1 {
public default void m1() {
System.out.println("P1.m1");
}
}
public static interface P2 {
public default void m2() {
System.out.println("P2.m2");
}
}
public static class C implements P1, P2 {
}
public static void main(String[] args) {
C o = new C();
o.m1();
o.m2();
}
}


C#:


// since .NET 4.8 / C# 8 (2019)

using System;

public class MI
{
public interface P1
{
public void M1()
{
Console.WriteLine("P1.M1");
}
}
public interface P2
{
public void M2()
{
Console.WriteLine("P2.M2");
}
}
public class C : P1, P2
{
}
public static void Main(string[] args)
{
C o = new C();
((P1)o).M1();
((P2)o).M2();
}
}

Gravatar #7 - arne_v
31. okt. 2023 14:14
Med hensyn til C++ og multiple implementation inheritance så tror jeg at dette eksemple illusterer kompleksiteten i C++'s måde a gøre det på.

C:\Work>g++ dod1.cpp -o dod1.exe

C:\Work>dod1
1 1

C:\Work>g++ dod2.cpp -o dod2.exe

C:\Work>dod2
2 2

dod1.cpp:


#include <iostream>

using namespace std;

class P
{
protected:
int myvalue;
public:
P() { myvalue = 0; }
};

class C1 : public P
{
public:
void IncVal() { this->myvalue++; }
int GetVal() { return this->myvalue; }
};

class C2 : public P
{
public:
void IncValue() { this->myvalue++; }
int GetValue() { return this->myvalue; }
};

class GC : public C1, public C2
{
};

int main()
{
GC *o = new GC();
o->IncVal();
o->IncValue();
cout << o->GetVal() << " " << o->GetValue() << endl;
return 0;
}


dod2.cpp:


#include <iostream>

using namespace std;

class P
{
protected:
int myvalue;
public:
P() { myvalue = 0; }
};

class C1 : virtual public P
{
public:
void IncVal() { this->myvalue++; }
int GetVal() { return this->myvalue; }
};

class C2 : virtual public P
{
public:
void IncValue() { this->myvalue++; }
int GetValue() { return this->myvalue; }
};

class GC : public C1, public C2
{
};

int main()
{
GC *o = new GC();
o->IncVal();
o->IncValue();
cout << o->GetVal() << " " << o->GetValue() << endl;
return 0;
}

Gravatar #8 - larsp
1. nov. 2023 11:34
#6 Ja, multiple implementation inheritance blev vel set som for-fronten inden for udvikling af programmeringssprog, og alle de store hjerner gik på sagen for at lave det mest fleksible og avancerede system de kunne drømme op.

Men det er noget tricky stads at få gjort rigtigt. Det kræver stærk kaffe at læse, er svært at refaktorere, det kan have overraskelser som fint demonstreret i #7 (har subklasserne hver deres myvalue, eller deles de om myvalue ??).

Og jeg vil mene at kode skal være letlæst og uden overraskelser for at være god kode ... så, dumpekarakter fra mig!

Jeg gravede lidt i Pythons nedarvning og multi-nedarvnings principper, og det er ikke så logisk og indlysende som jeg fik det til at lyde i #5. Jeg kører med en art "light" udgave af nedarvning i min kode. Skal man gøre det "rigtigt" skal man bruge super() operatoren så avancerede nedarvningstræer og kooperativ nedarvning kan understøttes. Men jeg må erkende at jeg ikke forstår det i dybden. Jeg har altid holdt mig fra at dyrke OOP på det niveau.
Gravatar #9 - arne_v
1. nov. 2023 14:24
larsp (8) skrev:
#6 Ja, multiple implementation inheritance blev vel set som for-fronten inden for udvikling af programmeringssprog, og alle de store hjerner gik på sagen for at lave det mest fleksible og avancerede system de kunne drømme op.

Men det er noget tricky stads at få gjort rigtigt. Det kræver stærk kaffe at læse, er svært at refaktorere, det kan have overraskelser som fint demonstreret i #7 (har subklasserne hver deres myvalue, eller deles de om myvalue ??).


For at forstå den kode skal man:
- læse kode meget grundigt for at se forskellen mellem " : public P" og " : virtual public P"
- vide nok om C++ til at vide hvad forskellen betyder

Jeg tror at der er mange som misser.

Og hvad sker der lige hvis C1 bruger virtual og C2 ikke gør??

Jeg aner det ikke. Jeg ville være nødt til enten at prøve eller læse C++ standarden (det er formentligt langt hurtigere at prøve!).

larsp (8) skrev:

Og jeg vil mene at kode skal være letlæst og uden overraskelser for at være god kode ... så, dumpekarakter fra mig!

Jeg gravede lidt i Pythons nedarvning og multi-nedarvnings principper, og det er ikke så logisk og indlysende som jeg fik det til at lyde i #5. Jeg kører med en art "light" udgave af nedarvning i min kode. Skal man gøre det "rigtigt" skal man bruge super() operatoren så avancerede nedarvningstræer og kooperativ nedarvning kan understøttes. Men jeg må erkende at jeg ikke forstår det i dybden. Jeg har altid holdt mig fra at dyrke OOP på det niveau.


Den tilgang har 99.95% af udviklere.

Fra et OO teoretisk perspektiv er det meget interessant med den C++ DoD pointe - at udvikleren kan bestemme om C1 og C2 skal have samme eller forskellig instans af P.

Og der skal nok være nogle udviklere som har haft brug for det.

Men det er formentligt få hundreder ud af en million C++ udviklere.

Det store flertal har ikke brug for den slags og går langt uden om den form for unødvendige komplikationer.

PS: Jeg kender ikke problemet fordi jeg har haft brug for det, men fordi jeg for en 10-15 år siden skulle lave et eksempel som viste problemer med DoD og kom op med et eksempel som jeg forsimplede lidt til #7.
Gravatar #10 - larsp
3. nov. 2023 07:35
arne_v (9) skrev:
Fra et OO teoretisk perspektiv er det meget interessant med den C++ DoD pointe - at udvikleren kan bestemme om C1 og C2 skal have samme eller forskellig instans af P.

Og der skal nok være nogle udviklere som har haft brug for det.

DoD, som i data oriented design, aka, en separation af data og funktioner? Er ikke bekendt med termen i denne sammenhæng.

.

Er der ikke to tankegange vedrørende nedarvning på spil:

1. Nedarver man med henblik på at have intakte superklasser der uforstyrret kan gøre deres sædvanlige ting med data, og bevare deres interne garantier.

2. eller nedarver man for at lave en ny, dybdegående ændret udgave af en klasse, typisk ved at implementere en partiel base klasse eller et interface.

For 1. vil jeg sige, okay, hvis man vil klistre lidt ekstra logik på en klasse, kan jeg godt se formålet. Men multiple inheritance med 1. er fuldstændigt sindsygt (dod2 i #7) Vil man lege med allerede færdige klasser som lego-klodser brug da composition ... i stedet for at lave en chimera :P

2. er en type OOP jeg godt kan li' og selv ofte bruger. Det er en måde at spare kode og opnå polymorphism og her synes jeg fint man kan bruge multiple inheritance ved at implementere flere partielle base klasser. Men det antager at alle klasserne er en del af det lokale source-tree, og er skrevet med henblik på nedarvning.
Gravatar #11 - arne_v
3. nov. 2023 11:45
#10

DoD som i Diamond of Death. Det lidt farverige navn for P, C1, C2, GC konstruktionen.
Gravatar #12 - arne_v
5. nov. 2023 18:45
#10

Det er så vigtigt at en klasse er beregnet til arv.

Java er specielt slem fordi alle instans metoder er virtuelle.

Det klassiske Joshua Bloch eksempel i min version er:


package october;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class MyArrayList<E> extends ArrayList<E> {
private static final long serialVersionUID = 1L;
private int addCount = 0;
@Override
public boolean add(E o) {
addCount++;
return super.add(o);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
MyArrayList<Integer> lst = new MyArrayList<Integer>();
lst.addAll(Arrays.asList(1, 2, 3));
for(int v : lst) {
System.out.println(v);
}
System.out.println(lst.getAddCount());
}

}


Skriver den 1 2 3 3 eller 1 2 3 6?

Det afhænger af om addAll metoden bruger add metoden eller ej.

D.v.s. at funktionaliteten af MyArrayList afhænger af en intern implementerinsg detalje i ArrayList.

Ikke godt.

(for de nysgerrige så returnerer alle de Java versioner jeg har testet 1 2 3 3 d.v.s. at addAll kalder ikke add)

Det kan gøres garanteret korrekt med composition. Det giver bare lidt mange linier:


package october;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

public class MyList<E> implements List<E> {
private List<E> del;
private int addCount = 0;
public void forEach(Consumer<? super E> action) {
del.forEach(action);
}
public int size() {
return del.size();
}
public boolean isEmpty() {
return del.isEmpty();
}
public boolean contains(Object o) {
return del.contains(o);
}
public Iterator<E> iterator() {
return del.iterator();
}
public Object[] toArray() {
return del.toArray();
}
public <T> T[] toArray(T[] a) {
return del.toArray(a);
}
public boolean add(E e) {
addCount++;
return del.add(e);
}
public boolean remove(Object o) {
return del.remove(o);
}
public boolean containsAll(Collection<?> c) {
return del.containsAll(c);
}

public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return del.addAll(c);
}
public boolean addAll(int index, Collection<? extends E> c) {
return del.addAll(index, c);
}
public boolean removeAll(Collection<?> c) {
return del.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return del.retainAll(c);
}
public void replaceAll(UnaryOperator<E> operator) {
del.replaceAll(operator);
}
public <T> T[] toArray(IntFunction<T[]> generator) {
return del.toArray(generator);
}
public void sort(Comparator<? super E> c) {
del.sort(c);
}
public void clear() {
del.clear();
}
public boolean equals(Object o) {
return del.equals(o);
}
public int hashCode() {
return del.hashCode();
}
public E get(int index) {
return del.get(index);
}
public E set(int index, E element) {
return del.set(index, element);
}
public void add(int index, E element) {
del.add(index, element);
}
public boolean removeIf(Predicate<? super E> filter) {
return del.removeIf(filter);
}
public E remove(int index) {
return del.remove(index);
}
public int indexOf(Object o) {
return del.indexOf(o);
}
public int lastIndexOf(Object o) {
return del.lastIndexOf(o);
}
public ListIterator<E> listIterator() {
return del.listIterator();
}
public ListIterator<E> listIterator(int index) {
return del.listIterator(index);
}
public List<E> subList(int fromIndex, int toIndex) {
return del.subList(fromIndex, toIndex);
}
public Spliterator<E> spliterator() {
return del.spliterator();
}
public Stream<E> stream() {
return del.stream();
}
public Stream<E> parallelStream() {
return del.parallelStream();
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
MyArrayList<Integer> lst = new MyArrayList<Integer>();
lst.addAll(Arrays.asList(1, 2, 3));
for(int v : lst) {
System.out.println(v);
}
System.out.println(lst.getAddCount());
}
}


Kotlin kan gøre det smart:


package other

class MyList<E>(val del: MutableList<E>) : MutableList<E> by del {
var addCount = 0
override fun add(o: E): Boolean {
addCount++
return del.add(o)
}
override fun addAll(c: Collection<E>): Boolean {
addCount += c.size
return del.addAll(c)
}
}

fun main() {
val lst = MyList<Int>(ArrayList<Int>())
lst.addAll(listOf(1, 2, 3));
for(v in lst) {
println(v);
}
println(lst.addCount);
}

Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login