驚きの内容:クラス テンプレートの継承とメンバー関数

前回の投稿「クラス テンプレート」で、それらの基本について説明しました。今日は、クラス テンプレートの継承と、クラス テンプレートのメンバー関数のインスタンス化について驚かせるかもしれません。

ここが最初の驚きです。少なくとも、私にとってはそれでした。

クラス テンプレートの継承メンバー関数は使用できません

簡単に始めましょう。

// inheritance.cpp

#include <iostream>

class Base{
public:
 void func(){ // (1)
 std::cout << "func\n";
 }
};

class Derived: public Base{
public:
 void callBase(){
 func(); // (2)
 }
};

int main(){

 std::cout << '\n';

 Derived derived;
 derived.callBase(); 

 std::cout << '\n';

}

クラス Base と Derived を実装しました。 Derived public は Base から派生しているため、そのメソッド callBase (2 行目) クラス Base のメソッド func で使用できます。わかりました、プログラムの出力に追加するものは何もありません.

Base をクラス テンプレートにすると、動作が完全に変わります。

// templateInheritance.cpp

#include <iostream>

template <typename T>
class Base{
public:
 void func(){ // (1)
 std::cout << "func\n";
 }
};

template <typename T>
class Derived: public Base<T>{
public:
 void callBase(){
 func(); // (2)
 }
};

int main(){

 std::cout << '\n';

 Derived<int> derived;
 derived.callBase(); 

 std::cout << '\n';

}

コンパイル エラーに驚かれるかもしれません。

エラーメッセージの「テンプレートパラメーターに依存する 'func' への引数はないため、'func' の宣言を使用できる必要があります」という行が最初のヒントです。 func は、その名前がテンプレート パラメーターに依存しないため、いわゆる非依存の名前です T .非依存の名前は、テンプレート定義の時点で検索およびバインドされます。したがって、コンパイラは from T 依存基底クラス Base を調べず、クラス テンプレートの外部で使用できる名前 func はありません。テンプレートのインスタンス化の時点で、依存する名前のみが検索され、バインドされます。

このプロセスは、2 フェーズ ルックアップと呼ばれます。特に、最初のフェーズは、依存しない名前の検索を担当します。 2 番目のフェーズは、従属名の検索を担当します。

名前ルックアップを依存基底クラスに拡張するには、3 つの回避策があります。次の例では、3 つすべてを使用しています。

// templateInheritance2.cpp

#include <iostream>

template <typename T>
class Base{
public:
 void func1() const {
 std::cout << "func1()\n";
 }
 void func2() const {
 std::cout << "func2()\n";
 }
 void func3() const {
 std::cout << "func3()\n";
 }
};

template <typename T>
class Derived: public Base<T>{
public:
 using Base<T>::func2; // (2)
 void callAllBaseFunctions(){

 this->func1(); // (1)
 func2(); // (2)
 Base<T>::func3(); // (3)

 }
};


int main(){

 std::cout << '\n';

 Derived<int> derived;
 derived.callAllBaseFunctions();

 std::cout << '\n';

}

<オール>
  • 名前を依存させる :1 行目の this->func1 の呼び出しは、これが暗黙的に依存しているため、依存しています。この場合、名前検索ではすべての基本クラスが考慮されます。
  • 名前を現在のスコープに導入: Base::func2 を使用する式 (2 行目) は、func2 を現在のスコープに導入します。
  • 名前を完全修飾して呼ぶ :func3 を完全修飾 (3 行目) で呼び出すと、仮想ディスパッチが中断され、新たな驚きが生じる可能性があります。
  • どのオプションを使用する必要がありますか?一般的に、私は func1 を作成する最初のオプションを好みます 従属: this->func1 .このソリューションは、基本クラスの名前を変更した場合でも機能します。

    最後に、これがプログラムの出力です。

    メンバー関数のインスタンス化は遅延

    レイジーとは、クラス テンプレートのメンバー関数のインスタンス化が必要な場合にのみ発生することを意味します。証拠?ここにいます。

    // lazy.cpp
    
    #include <iostream>
    
    template<class T> 
    struct Lazy{
     void func() { std::cout << "func\n"; }
     void func2(); // not defined (1)
    };
    
    int main(){
     
     std::cout << '\n';
     
     Lazy<int> lazy;
     lazy.func();
     
     std::cout << '\n';
     
    }
    

    クラス Lazy のメソッド func2 () (1) は宣言されているだけで定義されていませんが、コンパイラはプログラムを受け入れます。 func2 のため、メンバー関数の定義は必要ありません。

    メンバー関数のインスタンス化プロセスのこの怠惰には、2 つの興味深い特性があります。

    リソースを保存

    たとえば、クラス テンプレート Array2 をインスタンス化する場合 さまざまな型の場合、使用されるメンバー関数のみがインスタンス化されます。この怠惰は、非テンプレート クラス Array1 には当てはまりません。 . C++ Insights の例をお見せしましょう。

    // lazyInstantiation.cpp
    
    #include <cstddef> 
    
    class Array1 { 
     public: 
     int getSize() const { 
     return 10; 
     } 
     private: 
     int elem[10]; 
    };
    
    template <typename T, std::size_t N> 
    class Array2 { 
     public: 
     std::size_t getSize() const {
     return N;
     }
     private: 
     T elem[N]; 
    }; 
    
    
    int main() {
    
     Array1 arr;
     
     Array2<int, 5> myArr1;
     Array2<double, 5> myArr2; // (1) 
     myArr2.getSize(); // (2) 
    
    }
    

    メンバー関数 getSize() クラステンプレートの Array2 myArr2 に対してのみインスタンス化されます (1)。このインスタンス化は myArr2.getSize() の呼び出しによって引き起こされます (2).

    C++ Insights は真実を示します。次のスクリーンショットの重要な行は、40 行目と 59 行目です。

    クラス テンプレートの部分的な使用

    すべてのメンバー関数をサポートしていないテンプレート引数を使用して、クラス テンプレートをインスタンス化できます。これらのメンバー関数を呼び出さない場合は、すべて問題ありません。

    // classTemplatePartial.cpp
    
    #include <iostream>
    #include <vector>
    
    template <typename T> // (1) 
    class Matrix {
     public:
     explicit Matrix(std::initializer_list<T> inList): data(inList) {}
     void printAll() const { // (2)
     for (const auto& d: data) std::cout << d << " ";
     }
    private:
     std::vector<T> data;
    };
    
    int main() {
    
     std::cout << '\n';
    
     const Matrix<int> myMatrix1({1, 2, 3, 4, 5});
     myMatrix1.printAll(); // (3) 
    
     std::cout << "\n\n";
    
     const Matrix<int> myMatrix2({10, 11, 12, 13});
     myMatrix2.printAll(); // (4) 
    
     std::cout << "\n\n"; 
    const Matrix<Matrix<int>> myMatrix3({myMatrix1, myMatrix2}); // myMatrix3.printAll(); ERROR (5) }

    クラス テンプレート Matrix (1)はあえてシンプルに。型パラメータ T, があります std::vector にデータを保持する 、および std::initalizer_list で初期化できます . Matrix メンバー関数 printAll()をサポート すべてのメンバーを表示します。 (3) と (4) はその使用法を示しています。出力演算子は Matrix に対してオーバーロードされていません したがって、 を作成できます。 myMatrix3 他の Matrix オブジェクトをメンバーとして持っていますが、それらを表示できません。

    5 行目を有効にすると、約 274 行の非常に詳細なエラー メッセージが表示されます。

    次は?

    次回の投稿では、エイリアス テンプレートとテンプレート パラメータについて書きます。

    悪いマーケティング

    私は悪いマーケティングの仕事をしました。ここ数日、LeanPub で公開されている私の C++20 の本が物理的な形で入手できるかどうか、何人かの人から尋ねられました。確かに、一ヶ月からです。お好みの Amazon マーケットプレイスを選択してください。

    米国:https://www.amazon.com/dp/B09328NKXKUK:https://www.amazon.co.uk/dp/B09328NKXKDE:https://www.amazon.de/dp/B09328NKXKFR:https:// www.amazon.fr/dp/B09328NKXKES:https://www.amazon.es/dp/B09328NKXKIT:https://www.amazon.it/dp/B09328NKXKJP:https://www.amazon.co.jp/ dp/B09328NKXKCA:https://www.amazon.ca/dp/B09328NKXK


    No