Bir C ++ yöntemini işaretçi argümanlı bir C işlevine dönüştürmek kabul edilebilir bir kalıp mı?


16

ESP-32'de C ++ kullanıyorum. Bir zamanlayıcı kaydederken bunu yapmak zorundayım:

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

Burada zamanlayıcı çağırır soundCallback.

Ve bir görevi kaydederken aynı şey:

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

Böylece yöntem ayrı bir görevde başlatılır.

GCC beni bu dönüşümler hakkında her zaman uyarıyor, ancak planlandığı gibi çalışıyor.

Üretim kodunda kabul edilebilir mi? Bunu yapmanın daha iyi bir yolu var mı?

Yanıtlar:


47

A tamreinterpret_cast olarak bilmediğiniz sürece her zaman balıkNe yaptığınızı . Burada, kodunuz yalnızca GCC'nin C ++ yöntemleri için çağrı kuralı nedeniyle çalışır, ancak bu tanımsız davranış gibi ağır kokar. Özellikle üye işlevlerinin hiçbir şekilde normal işlev işaretçileriyle uyumlu olduğunu varsaymamalısınız.

Genel yaklaşım, bunun yerine dahili olarak C ++ yöntemini çağıran uygun imzalı bir C uyumlu işlev tanımlamak olacaktır. Örneğin:

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

Bu kadro gayet iyi çünkü biz void*sivri uçlu nesnenin tipine geri dönüyoruz .

Detaylar:

  • extern "C"bu işlevin dil bağlantısını belirtir . Dil bağlantısı ad yönetimini ve işlevin çağırma kuralını etkiler . Üye işlevlerinde C dili bağlantısı olamaz. Dil bağlantısı büyük ölçüde iç / dış bağlantıya diktir.

  • Bir geri arama için işlev "özel" olabilir, yani dahili bağlantıya sahip olabilir. C kodu hiçbir zaman geri aramayı adıyla ifade etmez. Yukarıdaki kod snippet'i, staticanahtar kelime aracılığıyla dahili bağlantıyı belirtir (statik bir yöntem değil!). Alternatif olarak, işlev anonim bir ad alanına yerleştirilmiş olabilir.

    extern "C"Ve static(iç bağlantı) arasındaki etkileşimlerden tam olarak emin değilim . Örneğin [dcl.link]ederim o kadar bu yorumlamak “Tüm fonksiyon tipleri, dış bağlantı ile fonksiyon adları ve dış bağlantı ile değişken adları bir dil bağı var” diyor tipi arasında my_timer_callbackC dili bağlantısı var, ama onun işlevi olduğunu adı değildir.

  • A static_castburada uygundur, çünkü türünün gerçek türünü biliyoruz argancak tür sistemi içinde ifade edemiyoruz. Buna karşılık a reinterpret_cast, bir bit desenini yeniden yorumlamak istediğimizde uygundur, örn. Sayısal bir türe bir işaretçi.

  • İşlevler sıradan nesneler değildir ve üye işlevleri daha da azdır. İşlev yalnızca gerçek türüyle (ve üye işlev işaretçileri için benzer şekilde) çağrıldığı sürece işlev işaretçisi türleri arasında yeniden yorum yapabilirsiniz. İşlev işaretleyicilerini başka türlere (örneğin, nesne işaretçileri veya geçersiz işaretçiler) atayabileceğiniz, uygulama tanımlıdır ( arka plan ). POSIX'te işlev işaretçileri arasında yayın yapar ve çalışmasına void*izin verilir dlsym(). (Üye) işlev işaretçileri içeren diğer yayınlar tanımlanmamıştır. Özellikle, üye fonksiyonlar ve fonksiyon işaretçileri arasındaki dökümler mümkün değildir.


1
std::bindNesne işaretçisini ilk yöntem bağımsız değişkeni olarak da kabul etmiyor mu ?
val diyor Reinstate Monica

5
@val Evet, ancak bu üye işlevlerin sıradan işlevlerle uyumlu olduğu anlamına gelmez, sadece bind () işlevi üye işlevlerini sıradan işlev nesnelerinden ayrı bir durum olarak işleyen INVOKE algoritmasını kullanır . işlev göstergeleri. Std :: bind () bir functor oluşturduğundan bu C ile arabirim oluşturmak için uygun değildir
amon

1
Başka bir soru: neden extern "C"burada ihtiyacım var? Bu durumda C bağlantısı önemli mi?
val diyor Reinstate Monica

5
@val Bu işlevi C'den çağırabilmek istiyorsanız, C çağırma kuralını kullanması gerekir. Bu, bu işlevi C dili bağlantısıyla bildirerek veya derleyiciye özgü uzantılarla (örneğin __attribute__((cdecl)), ancak bunu yapmayın) yapılabilir. C ++ işlevinin, C uyumlu bir çağrı kuralına sahip olduğu garanti edilmez (GCC'de genellikle iyi çalışır).
amon

4
@val extern "C"Resmi olarak neden gerekli olduğuna ilişkin ayrıntılar için , bkz. [dcl.link]"Farklı dil bağlantılarına sahip iki işlev türü, aksi halde aynı olsalar bile farklı türlerdir". ve [expr.call]"İşlev türü, işlev adı verilen işlevin işlev türünden farklı olan bir ifade aracılığıyla çağrıldığında tanımlanmamış davranışla sonuçlanır"
Ben Voigt

-1

Şahsen, bulduğum en uyumlu, uygulanması kolay ve anlaşılması kolay yaklaşım, yalnızca yöntemi dahili olarak çağıran (ve statik değilse, bunu yapmak için mevcut bir örneği başlatın veya kullanın). Adaptör Tasarım Deseninin bir çeşit varyasyonu olarak görülebilir.


6
Amon buna cevap vermedi mi?
Dronz

1
@Dronz ikinci bir okumadan sonra, evet, çoğunlukla bu. Okuduğum anda staticbunu bir yöntem olarak gördüm ve bir nedenden ötürü thisişaretçiyi ilk argüman olarak geçmediğini (ve std::bindgüçlendirilmiş kullanımına ilişkin aşağıdaki tartışma) geçmediğini fark ettim . Ama evet, kesinlikle haklısın! (Çifte cevap için özür dilerim!)
Jesus Alonso Abad

3
Evet, staticen az üç farklı, farklı anlamı var. Ve eğer dikkatli olmazsan onları karıştırırsın. staticHer biri kendi başına harika bir araç olduğundan, farklı kullanımları arasındaki farkları anlamak gerçekten yararlı olur .
cmaster
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.