4 类模板继承


类模板继承中有一些细节需要注意。

类模板继承的成员函数是不可用的

#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';

}

有两个类Derived和Base。Derived公有继承于Base,所以在callBase中可以调用func方法。

现在将Base变成一个模板。

#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';
}

那么编译将报错:

Compilation Failed

/usercode/file.cpp: In member function 'void Derived<T>::callBase()':
/usercode/file.cpp:16:9: error: there are no arguments to 'func' that depend on a template parameter, so a declaration of 'func' must be available [-fpermissive]
   16 |         func();                      // (2)
      |         ^~~~
/usercode/file.cpp:16:9: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)

func是一个不依赖于模板参数T的名称。不依赖于模板参数的名称会在模板定义时查找。所以编译器不会在Base<T>中查找,故找不到func方法。依赖模板参数的名称在模板实例化时查找

上面这个查找过程叫做Two Phase Lookup。第一个阶段负责查找非依赖的名称,第二阶段查找依赖模板参数的名称。

下面有3种方案来调用func方法:

#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) 引入func2到当前的作用域
  void callAllBaseFunctions(){

    this->func1();                   // (1) 隐式依赖,名称查找会考虑所有的基类
    func2();                         // (2)
    Base<T>::func3();                // (3) 

  }
};


int main(){

  std::cout << '\n';

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

  std::cout << '\n';

}

建议使用第一种方法,这样在基类名称改变时仍然有效。

成员函数的实例化是lazy的

lazy意味著类模板的成员函数仅仅在被使用时才会实例化。

#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';
    
}

虽然func2函数只有声明,没有定义,编译器仍然可以正常运行。
再举一个例子:

#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方法只为myArr2实例化,因为其调用了这个方法。
C++ Insights显示了生成的代码:

#include <cstddef> 

class Array1
{
  
  public: 
  inline int getSize() const
  {
    return 10;
  }
  
  
  private: 
  int elem[10];
  public: 
  // inline constexpr Array1() noexcept = default;
};



template <typename T, std::size_t N> 
class Array2 { 
 public: 
    std::size_t getSize() const {
        return N;
    }
  private: 
     T elem[N]; 
};

/* First instantiated from: insights.cpp:27 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array2<int, 5>
{
  
  public: 
  inline std::size_t getSize() const;
  
  
  private: 
  int elem[5];
  public: 
  // inline constexpr Array2() noexcept = default;
};

#endif


/* First instantiated from: insights.cpp:28 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array2<double, 5>
{
  
  public: 
  inline std::size_t getSize() const
  {
    return 5UL;
  }
  
  
  private: 
  double elem[5];
  public: 
  // inline constexpr Array2() noexcept = default;
};

#endif

int main()
{
  Array1 arr = Array1();
  Array2<int, 5> myArr1 = Array2<int, 5>();
  Array2<double, 5> myArr2 = Array2<double, 5>();
  myArr2.getSize();
  return 0;
}

由于有了lazy特性,我们可以使用不支持所有成员函数的模板参数来实例化类模板,只要我们不调用这些不支持的成员函数就没问题。例如:

#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有一个成员函数printAll来打印所有元素。(3)和(4)是正确的。由于Matrix没有重载输出操作符<<,所以我们可以创建myMatrix3,但是不能打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值