Backend C++

Kompilasi Nim menjadi C++ dengan nim cpp dan penautannya dengan kode C++.

Daftar isi

Nim dapat menargetkan dua bahasa tingkat rendah pada komputer desktop, yaitu C dan C++. Program yang sama dikompilasi dengan nim c untuk C atau nim cpp untuk C++ tanpa mengubah satu baris pun. Topik ini membandingkan kedua backend pada satu program statistik kecil, mengamati berkas antara yang dihasilkannya, lalu mengikat kelas C++ dengan importcpp memakai pola @, #, dan pragma constructor. Diagram pada bagian akhir merangkum ketiga jalur kompilasi Nim, termasuk jalur JavaScript.

Dua backend dari satu bahasa#

Backend adalah lapisan terakhir kompilator yang menerjemahkan representasi antara dari Nim menjadi bahasa lain. nim c dan nim cpp adalah alias dari opsi --backend:c dan --backend:cpp, sehingga pemilihannya menyangkut satu sakelar saja. Semantik bahasa Nim tidak berubah antara keduanya, termasuk model manajemen memorinya, sehingga program tetap ditulis dengan pengetahuan Nim, bukan pengetahuan C atau C++.

Yang berbeda adalah bahasa antara dan penyusun yang dipanggil. Backend C menghasilkan berkas .c lalu menyusunnya dengan gcc, sedangkan backend C++ menghasilkan berkas .cpp lalu menyusunnya dengan g++. Perbedaan itu baru terasa ketika kode luar perlu diikat, karena pustaka C++ memerlukan backend C++.

bash
# periksa penyusun C++ yang akan dipanggil backend cpp
which g++
text Keluaran
/usr/bin/g++

Program yang sama di dua backend#

Program berikut menghitung rata-rata lima pengukuran suhu. Ia hanya memakai fitur Nim standar sehingga sah untuk kedua backend, dan inilah syarat utama program pembanding yang adil.

nim statistik.nim
# statistik.nim — program kecil untuk membandingkan backend C dan C++
proc rataRata(data: seq[float]): float =
  var total = 0.0
  for nilai in data:
    total += nilai
  total / float(data.len)

when isMainModule:
  let pengukuran = @[21.5, 22.0, 20.8, 23.4, 21.9]
  echo "jumlah data: ", pengukuran.len
  echo "rata-rata: ", rataRata(pengukuran)
bash
# kompilasi dengan backend C lalu jalankan
nim c --hints:off -o:statistik_c statistik.nim
./statistik_c
# kompilasi ulang dengan backend C++ lalu jalankan
nim cpp --hints:off -o:statistik_cpp statistik.nim
./statistik_cpp
text Keluaran
jumlah data: 5
rata-rata: 21.919999999999998
jumlah data: 5
rata-rata: 21.919999999999998

Keluaran kedua eksekusi identik, karena kode Nim yang sama diterjemahkan ke dua bahasa antara yang berbeda tetapi bermakna sama. Nilai 21.919999999999998 juga memperlihatkan bahwa akumulasi pecahan biner tidak persis sama dengan desimalnya, dan kedua backend menampilkan angka yang sama karena keduanya memakai floating point 64 bit yang baku. Hasil translasi setiap backend dapat diamati langsung di nimcache dengan menyuruh kompilator berhenti sebelum penyusunan.

bash
# tampilkan berkas antara hasil translasi backend cpp di nimcache
nim cpp --hints:off --compileOnly --nimcache:nimcache statistik.nim
find nimcache -name "@mstatistik.nim.cpp"
text Keluaran
nimcache/@mstatistik.nim.cpp

Ukuran biner hasil keduanya juga dapat dibandingkan. Backend C++ menghasilkan biner lebih besar daripada backend C pada program ini karena header dan templat C++ yang tersertakan, dan angka pastinya bergantung pada versi penyusun beserta programnya.

bash
# bandingkan ukuran biner hasil kedua backend
stat -c "%n %s byte" statistik_c statistik_cpp
text Keluaran
statistik_c 143688 byte
statistik_cpp 154424 byte

Mengikat kelas C++ dengan importcpp#

Binding ke C++ memakai .importcpp, yaitu padanan .importc untuk dunia C++. Pola pada pragmanya menentukan bentuk pemanggilan yang dipancarkan. Karakter @ menempatkan seluruh argumen pemanggilan, karakter # menyisipkan satu argumen pada posisinya, dan pragma constructor menandai proc sebagai konstruktor yang mengembalikan objek baru. Pola #.jarak() berarti menyisipkan objek penerima lalu mengakses member jarak().

Kelas contoh berikut dibuat header-only, yaitu seluruh definisi berada di satu header sehingga tidak ada berkas objek terpisah yang perlu ditautkan. Ini pola yang lazim untuk pustaka C++ kecil.

cpp titik.h
// tidak-dijalankan
// titik.h — kelas C++ header-only untuk contoh importcpp
#ifndef TITIK_H
#define TITIK_H

#include <cmath>

class Titik {
public:
  double x;
  double y;

  Titik(double px, double py) : x(px), y(py) {}

  void geser(double dx, double dy) {
    x += dx;
    y += dy;
  }

  double jarak() const {
    return std::sqrt(x * x + y * y);
  }
};

#endif
nim titik.nim
## Binding importcpp untuk kelas Titik dari titik.h.
type
  Titik {.importcpp: "Titik", header: "titik.h", bycopy.} = object
    x: cdouble
    y: cdouble

proc titikBaru(px, py: cdouble): Titik {.
  importcpp: "Titik(@)", constructor, header: "titik.h".}

proc geser(t: var Titik, dx, dy: cdouble) {.
  importcpp: "geser", header: "titik.h".}

proc jarak(t: Titik): cdouble {.
  importcpp: "#.jarak()", header: "titik.h".}

var t = titikBaru(3.0, 4.0)
echo "jarak awal: ", t.jarak()
t.geser(1.0, 1.0)
echo "titik baru: ", t.x, ", ", t.y
echo "jarak akhir: ", t.jarak()
bash
# kompilasi binding kelas C++ dengan backend cpp
nim cpp --hints:off titik.nim
# jalankan program hasil kompilasi
./titik
text Keluaran
jarak awal: 5.0
titik baru: 4.0, 5.0
jarak akhir: 6.4031242374328485

Pragma bycopy pada deklarasi Titik menjamin objek diteruskan sebagai nilai seperti pada C++, bukan sebagai penunjuk. Konstruktor Titik(@) menerima pasangan koordinat, proc geser memutasi objek melalui parameter var, sedangkan jarak hanya membaca. Jarak awal 5.0 adalah hasil teorema Pythagoras untuk pasangan 3.0 dan 4.0, sedangkan jarak akhir memuat representasi pecahan 64 bit dari akar 41. Apabila kelas berada di dalam namespace, pola importcpp ditulis lengkap misalnya geo::Titik(@) bersama header yang sesuai.

Kesalahan umum#

Kesalahan yang paling sering menyeret proyek baru adalah menyusun binding importcpp dengan nim c. Kompilator memancarkan modul yang memakai importcpp sebagai C++, sementara sisa runtime tetap disusun sebagai C, dan penautan kedua dunia itu gagal dengan pesan berbentuk undefined reference to 'atpsystemdotnim_Init000()' yang diakhiri final link failed: bad value. Aturannya sederhana, yaitu begitu satu modul memakai importcpp, seluruh proyek disusun dengan nim cpp, bukan hanya modul bersangkutan.

Kesalahan kedua terjadi pada sisi tipe, bukan penautan. Proc yang memutasi objek C++ wajib menerima parameter var sebagaimana geser pada contoh, dan pemanggilannya dengan objek yang tidak dapat dimutasi ditolak kompilator sebagai type mismatch sebelum kode C++ pun tersentuh. Kesalahan ketiga menyangkut pustaka C++ yang tidak berbentuk header-only: kelas yang implementasinya terpisah di berkas .cpp meninggalkan simbol yang belum terdefinisi, sehingga berkas objeknya wajib disertakan lewat --passL pada saat penautan, sama seperti pola penautan pustaka C pada topik interop.

Rantai kompilasi dalam satu diagram#

Diagram berikut merangkum seluruh jalur yang dibahas pada modul ini. Satu berkas .nim yang sama dapat berakhir menjadi biner lewat C atau C++, atau menjadi berkas JavaScript untuk node dan browser. nimcache menyimpan berkas antara setiap backend, sehingga hasil translasi selalu dapat diperiksa ketika perilaku kompilasi perlu diselidiki.

berkas .nimkode C (nimcache)gccbinerkode C++ (nimcache)g++binerberkas .jsnodebrowsernim cnim cppnim js
Rantai kompilasi Nim menuju C, C++, dan JavaScript

Ringkasan#

  • nim c dan nim cpp memakai semantik Nim yang sama dan hanya berbeda pada bahasa antara serta penyusunnya, yaitu gcc atau g++.
  • Berkas antara di nimcache memperlihatkan hasil translasi setiap backend dan menjadi alat penyelidikan pertama.
  • importcpp mengikat kelas C++ dengan pola @ untuk seluruh argumen, # untuk satu argumen, pragma constructor untuk konstruktor, dan bycopy untuk semantik nilai.
  • Kelas header-only menghindari penautan manual, sedangkan kelas yang terpisah ditautkan melalui --passL berkas objeknya.

Referensi#