Saya pikir saya mengerti keterbatasan sebenarnya dari polimorfisme waktu kompilasi dan polimorfisme run-time. Tapi apa perbedaan konseptual antara antarmuka eksplisit (run-time polymorphism. Yaitu fungsi virtual dan pointer / referensi) dan antarmuka implisit (compile-time polymorphism, mis. Templates) .
Pikiranku adalah bahwa dua objek yang menawarkan antarmuka eksplisit yang sama harus memiliki tipe objek yang sama (atau memiliki leluhur yang sama), sedangkan dua objek yang menawarkan antarmuka implisit yang sama tidak perlu jenis objek yang sama, dan, tidak termasuk implisit antarmuka yang mereka berdua tawarkan, dapat memiliki fungsi yang sangat berbeda.
Ada pemikiran tentang ini?
Dan jika dua objek menawarkan antarmuka implisit yang sama, alasan apa (di samping manfaat teknis tidak memerlukan pengiriman dinamis dengan tabel fungsi pencarian virtual, dll) yang ada karena tidak memiliki benda-benda ini mewarisi dari objek dasar yang menyatakan antarmuka itu, dengan demikian menjadikannya antarmuka yang eksplisit ? Cara lain untuk mengatakannya: dapatkah Anda memberi saya contoh di mana dua objek yang menawarkan antarmuka implisit yang sama (dan karenanya dapat digunakan sebagai tipe ke kelas templat contoh) tidak boleh mewarisi dari kelas dasar yang membuat antarmuka itu eksplisit?
Beberapa pos terkait:
- https://stackoverflow.com/a/7264550/635125
- https://stackoverflow.com/a/7264689/635125
- https://stackoverflow.com/a/8009872/635125
Berikut ini contoh untuk menjadikan pertanyaan ini lebih konkret:
Antarmuka Tersirat:
class Class1
{
public:
void interfaceFunc();
void otherFunc1();
};
class Class2
{
public:
void interfaceFunc();
void otherFunc2();
};
template <typename T>
class UseClass
{
public:
void run(T & obj)
{
obj.interfaceFunc();
}
};
Antarmuka Eksplisit:
class InterfaceClass
{
public:
virtual void interfaceFunc() = 0;
};
class Class1 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc1();
};
class Class2 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc2();
};
class UseClass
{
public:
void run(InterfaceClass & obj)
{
obj.interfaceFunc();
}
};
Contoh konkret yang lebih mendalam:
Beberapa masalah C ++ dapat diselesaikan dengan:
- kelas templated yang tipe templatnya menyediakan antarmuka implisit
- kelas non-templated yang mengambil pointer kelas dasar yang menyediakan antarmuka eksplisit
Kode yang tidak berubah:
class CoolClass
{
public:
virtual void doSomethingCool() = 0;
virtual void worthless() = 0;
};
class CoolA : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that an A would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
class CoolB : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that a B would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
Kasus 1 . Kelas non-templated yang mengambil pointer kelas dasar yang menyediakan antarmuka eksplisit:
class CoolClassUser
{
public:
void useCoolClass(CoolClass * coolClass)
{ coolClass.doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Kasus 2 . Kelas templated yang tipe templatnya menyediakan antarmuka implisit:
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser<CoolClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Kasus 3 . Kelas templated yang tipe templatnya menyediakan antarmuka implisit (kali ini, bukan berasal dari CoolClass
:
class RandomClass
{
public:
void doSomethingCool()
{ /* Do cool stuff that a RandomClass would do */ }
// I don't have to implement worthless()! Na na na na na!
}
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
RandomClass * c1 = new RandomClass;
RandomClass * c2 = new RandomClass;
CoolClassUser<RandomClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Kasus 1 mengharuskan objek yang dilewatkan useCoolClass()
menjadi anak dari CoolClass
(dan mengimplementasikan worthless()
). Kasus 2 dan 3, di sisi lain, akan mengambil kelas apa pun yang memiliki doSomethingCool()
fungsi.
Jika pengguna kode selalu subclass baik-baik saja CoolClass
, maka Kasus 1 masuk akal secara intuitif, karena CoolClassUser
akan selalu mengharapkan implementasi dari a CoolClass
. Tetapi anggap kode ini akan menjadi bagian dari kerangka kerja API, jadi saya tidak dapat memprediksi apakah pengguna ingin membuat subkelas CoolClass
atau memutar kelas mereka sendiri yang memiliki doSomethingCool()
fungsi.
sumber
Jawaban:
Anda telah mendefinisikan poin penting-satu adalah run-time dan yang lainnya adalah compile-time . Informasi nyata yang Anda butuhkan adalah konsekuensi dari pilihan ini.
Kompositime:
virtual
warisan atau gangguan lain dengan antarmuka implisit - keuntungan besar.Runtime:
Diberikan daftar relatif, jika Anda tidak memerlukan keunggulan spesifik run-time inheritance, jangan gunakan itu. Ini lebih lambat, kurang fleksibel, dan kurang aman dibandingkan template.
Sunting: Perlu dicatat bahwa di C ++ khususnya ada kegunaan untuk pewarisan lainnya dari run-time polymorphism. Misalnya, Anda bisa mewarisi typedef, atau menggunakannya untuk menandai, atau menggunakan CRTP. Namun, pada akhirnya, teknik-teknik ini (dan lainnya) benar-benar berada di bawah "Waktu kompilasi", meskipun mereka diimplementasikan menggunakan
class X : public Y
.sumber