跟我学C++中级篇——重载问题分析之类函数重载的问题

一、C++中的类成员函数重载

重载中的一个重要方面就是类中的成员函数的重载。从原则上来说,类成员函数的重载和普通函数的重载并没有什么不同。但在类的应用中,会有一些特定的方法和限定符等,这就需要处理它们在重载时的一些具体情况。如果不能够了解这一方面的知识,可能就会犯下一些低级的错误。下面将对其中的一些细节进行展开分析并举例说明。

二、类中的函数重载的主要问题

这里不会对类成员函数的重载再次进行分析说明,只对其中的一些重点问题进行说明:

  1. 类成员函数与普通函数的重载问题
    这种问题往往是新手才会发生,对作用域的理解不深刻。其实局部作用域会隐藏更高作用域的函数,从而根本无法形成重载。
  2. const限定符的重载问题
    const限定符的重载问题有两类,一种是是参数限制的控制;另外一种是常函数的重载控制。
  3. 引用限定符的重载问题
    引用限定符是C++11推出的一个技术点,如果没有这咱应用经验的可能会犯一些错误,但在真正掌握后就应该不会了。
  4. 类继承中的重载问题
    类继承中的重载问也有两种情况,一种是虚函数的重载和父子类的隐藏问题;另外一种是重载函数调用的父子类切片导致的问题

三、分析和解决

解决上述的问题其实相对来说比较简单,但实现起来可能还是让开发者会有些感觉复杂,针对的具体的问题解决方法如下:

  1. 类成员函数与普通函数的重载问题
void print(int &x) { std::cout << "reference" << std::endl; }
void print(const int &x) { std::cout << "const reference" << std::endl; }
class Demo {
public:
  // void print(int v) {}//注意,这就是上文提到的const的顶层无法形成重载,但底层的引用和指针可以,上文举的指针,本例使用引用
  void print(int &x) { std::cout << "mem reference" << std::endl; }
  void print(const int &x) { std::cout << "mem const reference" << std::endl; }
  void print(int &&x) { std::cout << "mem l_reference" << std::endl; }
  void print(const int &&x) { std::cout << "mem const l_reference" << std::endl; }
};

其实这就是作用域的限制,类成员函数只会隐藏外面的普通函数,无法形成重载。只要抓住作用域处理的逻辑即可解决此类问题。
2. const限定符的重载问题
const参数控制:

#include <iostream>

class Demo {
public:
  // void print(int v) {}//注意,这就是上文提到的const的顶层无法形成重载,但底层的引用和指针可以,上文举的指针,本例使用引用
  void print(int &x) { std::cout << "reference" << std::endl; }
  void print(const int &x) { std::cout << "const reference" << std::endl; }
  void print(int &&x) { std::cout << "l_reference" << std::endl; }
  void print(const int &&x) { std::cout << "const l_reference" << std::endl; }
};
int main() {
  int a = 10;
  const int b = 20;
  Demo d;

  d.print(a);            // non-const lvalue
  d.print(b);            // const lvalue
  d.print(30);           // rvalue
  d.print(std::move(b)); // const rvalue
  return 0;
}

常函数的控制:

class Demo {
public:
  void fun() { std::cout << "call member  function!"<<std::endl; }
  void fun() const { std::cout << "call const function!"<<std::endl; }
};
void TestConstFunc() {
  Demo d0;
  d0.fun();

  const Demo d1;//注意常量定义
  d1.fun();
}

这类问题主要在于未能准确的把握const的实际使用的顶层和底层区别以及在类成员中常函数和非常函数的不同。只要掌握了这些技术点,解决就非常简单了。
3. 引用限定符的重载问题
仍然看一下上文已经给出的例子:

class CVDemo {
private:
    std::string str_;
    
public:
    CVDemo(const std::string& n) : str_(n) {}
     
    //引用限定符重载
    void control() & {
        std::cout << "call control & => lvalue" << std::endl;
    }
    
    void control() && {
        std::cout << "call control && =>rvalue" << std::endl;
    }
    
    void control() const & {
        std::cout << "call control const & => const lvalue" << std::endl;
    }
};

引用限定符的问题与const类似,主要的一个问题在于它出现于C++11之中,可能让不少开发者对此不太了解,而且确实一般程序员用得也比较少。不过,遇到问题后查查相关的技术资料就应该没有什么问题了。
4. 类继承中的重载问题
类继承中的虚函数的重载和父子类的隐藏问题:

// 继承中的重载 
class Base {
public:
    //虚函数重载
    virtual void run(int d) {
        std::cout << "call Base::run(int): " << d << std::endl;
    }
    
    virtual void run(double d) {
        std::cout << "call Base::run(double): " << d << std::endl;
    }
    
    void genFunc(int d) {
        std::cout << "Base::genFunc(int): " << d << std::endl;
    }
    
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    // 隐藏基类重载 
    void run(int x) override {
        std::cout << "Derived::run(int): " << x << std::endl;
    }
    
    // 引入基类重载的解决方案
    using Base::run;
    
    void genFunc(const std::string& s) {
        std::cout << "Derived::genFunc(string): " << s << std::endl;
    }
};

对这种隐藏问题,需要开发者显式的指定对象的类型。要特别注意父子类中的一些特定的用法如多态等的处理。只要掌握这些技术点,就没有什么大问题了。对用显式的使用父类函数可以使用using进行显式的场景即可(using Base::func())
父子类切片导致的问题:

class Base {
public:
  int data_[10000];
  virtual void run() { std::cout << "call base run" << std::endl; }
  virtual ~Base() = default;
};

class Derived : public Base{
public:
  int ddata_[100000];  

  Derived() {}

  void run() override { std::cout << "call Derived run  "  << std::endl; }

  ~Derived() {}
};

// 危险的重载函数
void callTest(Base b) { // 对象切片!
  std::cout << "call Base obj"<< std::endl;
  b.run();  
}

void callTest(Base *obj) {  
  std::cout << "call Base obj* " << std::endl;
  b->run(); // 正确 
}

int main() {
  Derived *d = new Derived();

  //下面的调用有问题
  callTest(*d);  
  callTest(d);  

  delete d; //可能有问题 
  return 0;
}

上面的代码的主要问题,主要原因其实并不是重载引起的,而是父子类中虚析构函数以及重写(重写与重载的区别)导致的可能出现的内存和资源的处理问题。但这些问题往往交织在一起,还有可能导致程序的崩溃,这就需要开发者从整体上进行把握和考虑了。特别是对类中的重载、隐藏和重写要时刻有一个清晰的认知(建议回头翻看一下“重载重写覆盖和隐藏”)。

四、总结

类中的重载函数处理,相对要复杂一些,但只要抓住其中的关键点,仔细分析,这其实都不算是什么大问题。真正的大问题在于,可能从错误的结果回推时,往往没有注意这到这些技术细节,而偏向了另外的方向。导致浪费了不少的时间和精力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值