C++ im Vergleich zu RUST

(Zurück zu: Infos)

  1. Einleitung

Einleitung

Mir wurde kürzlich ein 3-tägiger Rust-Crashkurs angeboten, um die Programmiersprache kennen zu lernen.
Da sie sich an einigen Stellen sehr von C++ unterscheidet, dachte ich, dass es Sinn macht, die gleiche Funktionalität in beiden Sprachen zu implementieren.

So kann man direkt die Syntax vergleichen. Schließlich sagt ein Code mehr tausend Wort.

Hello World

1//C++
2int main()
3{
4  printf("Hello World\n");
5  return 0;
6}
1//Rust
2fn main() {
3  println!("Hello World");
4  std::process::exit(0);
5}

Funktionen und Parameter

 1//C++
 2static double add(int a, float b)
 3{ // local function
 4  double d = a + b;
 5  return d;
 6}
 7
 8void run_add()
 9{ // public extern function
10  double d = add(12, 34.0f);
11  printf("%lf\n", d);
12}
 1fn add(a: i32, b: f32) -> f64 {
 2  // local function
 3  let d = a as f64  + b as f64;
 4  // last line without ";" is equal to "return d;"
 5  d
 6}
 7
 8pub fn run_add() {
 9  let d = add(12, 34.0);
10  println!("{}", d);
11}

Objekte

  • In C++ werden Klassen definiert aus denen dann Objektinstanzen zur Laufzeit erzeugt werden.
  • Objektinstanzen einer Klasse können Membervariablen verwalten.
  • Eine Konstruktorfunktion initialisiert eine Objektinstanz.
  • Klassenmethoden können statisch (Klassen-bezogen, siehe get_type_description) und nicht-statisch (Objekt-bezogen) sein.
  • Nicht-statische Methoden können als const qualifiziert sein und dürfen den Objektinhalt nicht ändern (siehe get_foo). Nicht-const Methoden dürfen den Inhalt ändern (siehe set_bar).
 1//C++
 2class Thing
 3{
 4private:
 5  int m_foo;
 6  int m_bar;
 7public:
 8  Thing(int foo)
 9  : m_foo(foo),
10    m_bar(0)
11  {    
12  }
13  static char const* get_type_description()
14  {
15    return "This is a Thing type.";
16  }
17  int get_foo() const
18  {
19    return this->m_foo;
20  }
21  int get_bar() const
22  {
23    return this->m_bar;
24  }
25  void set_bar(int value)
26  {
27    m_foo = value;
28  }
29};
30int use_thing()
31{
32  int foo = 12;
33  Thing thing(foo);
34  thing.set_bar(13);
35  int n = thing.get_foo() + thing.get_bar();
36  printf("n = %d\n", n);
37  return 0;
38}
  • Rust kennt keine Klassen, sonder definiert structs für zusammengehörige Variablen.
  • Für structs werden Funktionen implementiert (impl), die frei assoziiert sind (siehe get_type_description), oder als Methode an eine struct Instanz gebunden sind (siehe get_foo).
  • Erzeugt werden struct Instanzen häufig über eine assoziierte new Funktion, die das Objekt initialisiert. Die Erzeugung kann innerhalb eines Rust Modules aber auch direkt als Struct { member: initvalue, member2: initvalue2 } erfolgen.
  • Methoden dürfen den Inhalt der struct nur ändern, wenn sie als mut auf die self Referenz zugreifen (siehe set_bar), ansonsten dürfen die Membervariablen nur gelesen werden (siehe get_bar).
 1//Rust
 2struct Thing {
 3  m_foo: i32,
 4  m_bar: i32
 5}
 6
 7impl Thing {
 8  pub fn new(foo: i32) -> Self {
 9    // return a new constructed Thing
10    Self {
11      m_foo: foo,
12      m_bar: 0
13    }
14  }
15  pub fn get_type_description() -> &'static str {
16    // return a static immutable string reference
17    "This is a Thing type."
18  }
19
20  pub fn get_foo(&self) -> i32 {
21    // does not mutate object
22    // full return statement
23    return self.m_foo;
24  }
25  pub fn get_bar(&self) -> i32 {
26    // does not mutate object
27    // short return statement
28    self.m_bar
29  }
30  pub fn set_bar(&mut self, bar: i32) {
31    // mutates object
32    self.m_bar = bar;
33  }
34}
35
36pub fn use_thing() {
37  let foo = 12;
38  let mut thing = Thing::new(foo);
39  thing.set_bar(13);
40  let n = thing.get_foo() + thing.get_bar();
41  println!("Description = {}", Thing::get_type_description());
42  println!("n = {}", n);
43}
44

Interfaces

In C++ werden Schnittstellen und Polymorphismus durch virtuell Methoden in Klassen definiert. Implementiert werden Schnittstellen dann durch Ableitung einer neuen Klasse von der Interface-Klasse.

 1class Animal
 2{
 3public:
 4  virtual ~Animal() {}
 5  virtual std::string get_name() const = 0;
 6  virtual void speak() const = 0;
 7};
 8class Cat : public Animal
 9{
10private:
11  std::string m_name;
12public:
13  Cat(char const* name)
14  : m_name(name)
15  {
16  }
17  std::string get_name() const override
18  {
19    return m_name;
20  }
21  void speak() const override
22  {
23    printf("Meow\n");
24  }
25};
26class Dog : public Animal
27{
28private:
29  std::string m_name;
30public:
31  Dog(char const* name)
32  : m_name(name)
33  {
34  }
35  std::string get_name() const override
36  {
37    return m_name;
38  }
39  void speak() const override
40  {
41    printf("Meow\n");
42  }
43};
44static void talk_with_animal(Animal const& animal) 
45{
46  std::cout << "Animals's name is: " << animal.get_name();
47  animal.speak();
48}
49
50void handle_animals() 
51{
52  Cat c("Kitty");
53  Dog d("Sparky");
54  
55  talk_with_animal(c);
56  talk_with_animal(d);
57}

In Rust kann jede Struktur über das dyn Schlüsselwort zu einer Schnittstelle gemacht werden. Aufrufe von Methoden eines dyn Objektes werden “dynamisch” durchgeführt. Ähnlich wie bei C++ werden dann Methodentabellen eingesetzt anstatt die Implementierungen direkt aufzurufen.
Auf diese Weise lassen sich polymorphe Zugriffe umsetzen.

 1pub trait Animal {
 2  fn get_name(&self) -> String;
 3  fn speak(&self);
 4}
 5
 6struct Cat {
 7  m_name: String
 8}
 9
10impl Cat {
11  pub fn new(name: &str) -> Self {
12    Self {
13      m_name: name.to_string()
14    }
15  }
16}
17
18impl Animal for Cat {
19  fn get_name(&self) -> String {
20    self.m_name.clone()
21  }
22  fn speak(&self) {
23    println!("Meow");
24  }
25}
26
27struct Dog {
28  m_name: String
29}
30
31impl Dog {
32  pub fn new(name: &str) -> Self {
33    Self {
34      m_name: name.to_string()
35    }
36  }
37}
38
39impl Animal for Dog {
40  fn get_name(&self) -> String {
41    self.m_name.clone()
42  }
43  fn speak(&self) {
44    println!("Woof");
45  }
46}
47
48fn talk_with_animal(animal: &dyn Animal) {
49  println!("Animals's name is: {}", animal.get_name());
50  animal.speak();
51}
52
53pub fn handle_animals() {
54  let c = Cat::new("Kitty");
55  let d = Dog::new("Sparky");
56
57  let a: &dyn Animal = &c;
58  talk_with_animal(a);
59
60  let a: &dyn Animal = &d;
61  talk_with_animal(a);
62}
63

Operatoren für Typen

In C++ können für alle (nicht-primitiven) Typen Operatorfunktionen überladen werden. Somit lassen sich Objekte mit +, -, *, / wie auch anderen Zeichen verknüpfen. Operatorfunktionen können entweder in einer Klasse oder global definiert werden. Wird operator+(T, T) für einen Typen implementiert, kann im Code
T t3 = t1 + t2; den entsprechenden Additionscode aufrufen.

 1//C++
 2struct Point
 3{
 4  float x;
 5  float y;
 6
 7  Point(float xx, float yy) 
 8  : x(xx), y(yy)
 9  {    
10  }
11}
12
13Point operator+(Point const& l, Point const& r)
14{
15  return Point(l.x + r.x, l.y + r.y);
16}
17Point& operator+=(Point& l, Point const& r)
18{
19  l.x += r.x;
20  l.y += r.y;
21  return l;
22}
23
24Point operator-(Point const& l, Point const& r)
25{
26  return Point(l.x - r.x, l.y - r.y);
27}
28Point& operator-=(Point& l, Point const& r)
29{
30  l.x -= r.x;
31  l.y -= r.y;
32  return l;
33}
34
35Point operator*(Point const& l, float const& factor)
36{
37  return Point(l.x * factor, l.y * factor);
38}
39Point& operator*=(Point& l, float const& factor)
40{
41  l.x *= factor;
42  l.y *= factor;
43  return l;
44}
45
46Point operator/(Point const& l, float const& denominator)
47{
48  return Point(l.x / denominator, l.y / denominator);
49}
50Point& operator/=(Point& l, float const& denominator)
51{
52  l.x /= denominator;
53  l.y /= denominator;
54  return l;
55}
56

In Rust werden Operatoren durch Traits implementiert. Diese liegen in std:ops. Wird das Add Trait für einen Typen T implementiert, kann im Code per
let t3 = t1 + t2; der implementierte Additionscode ausgeführt werden.

  1// Rust
  2use std::ops::Add;
  3use std::ops::AddAssign;
  4use std::ops::Sub;
  5use std::ops::SubAssign;
  6use std::ops::Mul;
  7use std::ops::MulAssign;
  8use std::ops::Div;
  9use std::ops::DivAssign;
 10
 11// struct must derive Copy and Clone, 
 12// otherwise operator parameters are consumed and cannot be used again
 13#[derive(Copy, Clone)]
 14pub struct Point {
 15  pub x: f32,
 16  pub y: f32
 17}
 18
 19impl Point {
 20  pub fn new(x: f32, y: f32) -> Self {
 21    Self { 
 22      x: x, 
 23      y: y
 24    }
 25  }
 26}
 27
 28impl Add for Point {
 29  type Output = Point;    
 30  fn add(self, other: Self) -> Self::Output {
 31    Point::new(self.x + other.x, self.y + other.y)
 32  }
 33}
 34impl AddAssign for Point {
 35  fn add_assign(&mut self, other: Self) {
 36    self.x += other.x;
 37    self.y += other.y;
 38  }
 39}
 40impl Sub for Point {
 41  type Output = Self;
 42      
 43  fn sub(self, other: Self) -> Self::Output {
 44    Point::new(self.x - other.x, self.y - other.y)
 45  }
 46}
 47impl SubAssign for Point {
 48  fn sub_assign(&mut self, other: Self) {
 49    self.x -= other.x;
 50    self.y -= other.y;
 51  }
 52}
 53impl Mul<f32> for Point {
 54  type Output = Self;
 55      
 56  fn mul(self, factor: f32) -> Self::Output {
 57    Point::new(self.x * factor, self.y * factor)
 58  }
 59}
 60impl MulAssign<f32> for Point {
 61  fn mul_assign(&mut self, factor: f32) {
 62    self.x *= factor;
 63    self.y *= factor;
 64  }
 65}
 66impl Div<f32> for Point {
 67  type Output = Self;
 68      
 69  fn div(self, denominator: f32) -> Self::Output {
 70    Point::new(self.x / denominator, self.y / denominator)
 71  }
 72}
 73impl DivAssign<f32> for Point {
 74  fn div_assign(&mut self, denominator: f32) {
 75    self.x /= denominator;
 76    self.y /= denominator;
 77  }
 78}
 79        
 80pub fn use_point_operators() {
 81  let p1 = Point::new(1.0, 2.0);
 82  let p2 = Point::new(2.0, 3.0);
 83    
 84  let p3 = p1 + p2;
 85  assert_eq!(p3.x, p1.x + p2.x);
 86  assert_eq!(p3.y, p1.y + p2.y);
 87
 88  let p4 = p2 - p1;
 89  assert_eq!(p4.x, p2.x - p1.x);
 90  assert_eq!(p4.y, p2.y - p1.y);
 91
 92  let p5 = p2 * 10.0;
 93  assert_eq!(p5.x, p2.x * 10.0);
 94  assert_eq!(p5.y, p2.y * 10.0);
 95
 96  let p6 = p5 / 5.0;
 97  assert_eq!(p6.x, p5.x / 5.0);
 98  assert_eq!(p6.y, p5.y / 5.0);
 99}

Fortsetzung folgt


(Zurück zu: Infos)
📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!