去年遇到了一个难以理解的bug,这个问题简化起来是这样:
template<class T>
class
A { public: friend void test(int x) { cout << 1; } private: T a; };

  需求是,test函数想要成为A类的友元函数,来读取A类中的private成员属性,同时A类是一个泛型类,所以理所应当地想到这种写法。在A类还没有实例化对象的时候,这种写法没有任何报错,当A类创建对象的时候,出现了奇怪的bug:

int main() {
    A<int> x;
    A<long long> y;
    test(1);
    cout << "没有问题" << endl;
}

  A类创建了两个对象,A类是泛型类,创建了一个int型的类,与一个long long型的类,结果出现了这个的报错:

   redefinition,说明void test重定义了,但是为什么?还有更诡异的现象:当x,与y同时存在的时候,就会报这样的错,但是当x,y只存在一个的时候,也就是A类只创建一种类型的对象时,它并不会报错,可以正常运行。当时也没有深究这个问题,但到了现在,随着本人对静态多态的理解加深,这个问题可以得到解答。

  首先,先来了解一下c++的泛型:template的实现原理,简单总结起来,就是五个字:自动生成代码。

  之所以泛型会被称为静态多态,是因为它并不是运行时来进行,而是在编译期进行的。假如定义了一个泛型函数test:

void test(T t1, T t2) {
  //pass
};

  在没有对它实体化之前,它只是一个模板,而不是一个函数,编译器就可以认为没有test这个函数。但是当使用到它的时候,编译器会根据test函数的使用情况自动地生成相应的参数的函数,比如下面,main函数中使用到了double参数类型的test与int参数类型的test:

int main() {
  double x1 = 0, x2 = 0;
  int y1, y2;
  test(x1 = 0, x2 = 0);
  test(y1 = 0, y2 = 0);
  return 0;
}

  编译器会检测到你一共使用了int与double两种参数的test,所以自动生成一个如下代码:

void test(int t1, int t2) {
    //pass
};
void test(double t1, double t2) {
    //pass
};

  这些事本来是由程序员自己来完成的,用template就可以偷懒,给你一个模板,让编译器自己生成代码。这也是为什么,c++把它叫模板。因为template的函数或是类本身就不是一个函数或者类,它只是一个模板,告诉编译器该怎么生成代码,它本质上就是一种宏的替换,把template<class T>中的T替换成程序中所使用的类型,而这些操作就是在编译期时完成的,而不是在运行时进行的,因为相应的代码已经生成好了,不会有运行时开销,所以称为“静态多态”。

  这样做当然会有缺点,就是程序的编译时间会变长,生成的可执行文件会变大,但是用编译时间去换运行时间是很划算的,因为编译只需要编译一次,而一个程序要被运行很多次。

  再说回之前的那个问题,知道了template的原理之后,之前的问题也就行容易理解了:在使用了A<int> x;的时候,编译器检测到了使用了int,所以生成了如下代码:

class A {
public:
    friend void test(int x) {
        cout << 1;
    }
private:
    int a; //将template<class T>中的T替换成int.
};

  然后又检测到了使用了A<long long> y;所以又生成了如下代码:

class A {
public:
    friend void test(int x) {
        cout << 1;
    }
private:
    long long a; //将template<class T>中的T替换成long long.
};

  发现问题了吗?void test(int x)被定义了两次,成员函数是可以定义两次的,因为A<int>与A<long long>是两个不同的类,它们有一个名字相同的成员函数当然没有问题,但是友元函数不同,友元函数并不属于这个类,它属于一个独立的函数或是其它类的成员函数。所以出现了两次它的定义,肯定会报redefinition的错。

  解决方法当然也很简单,一个函数,可以不能定义两次,但是可以被申明无数次:类内声类,类外实现:

#include<iostream>
using namespace std;
template<class T>
class A {
public:
    friend void test(int x);
private:
    T a; //将template<class T>中的T替换成long long.
};
void test(int x) {
    cout << 1;
}
int main() {
    A<int> x;
    A<long long> y;
    test(1);
    cout << endl << "没问题" << endl;
    return 0;
}

   运行结果:

 

posted on 2023-03-26 17:44  Kuyenda  阅读(18)  评论(0编辑  收藏  举报